Open Manu343726 opened 9 years ago
So http://blogs.msdn.com/b/vcblog/archive/2015/04/29/c-11-constant-expressions-in-visual-studio-2015-rc.aspx .... Fixed array approach is not valid for msvc.
If the language would allow us to have static variables in constexpr functions, this could solve the problem. But sadly, it isn't legal for us - although the compiler can do it, that is how PRETTY_FUNCTION is implemented. This is so unfair. :(
Another solution would be to wrap the string into a variadic template, i.e.:
template<char... Str>
struct string
{
static constexpr const char value[] = {Str..., '\0'};
};
Since C++14 array subscript is considered a constexpr
expression, so "hello"[0]
should be a valid non-type template parameter. Sadly, constexpr
function parameters are not considered to be constexpr
:( I have never understood this rule...
I wrote a horrible code which might even be UB:
#include <cstddef>
template <std::size_t N> // w/o null-terminator
struct string_storage : string_storage<N - 1>
{
char value;
constexpr string_storage(const char *str)
: string_storage<N - 1>(str - 1), value(*str) {}
};
template <>
struct string_storage<0u>
{
char value;
constexpr string_storage(const char *str)
: value(*str) {}
};
constexpr const char* c_str(const string_storage<0> &string)
{
return &string.value;
}
#include <iostream>
int main()
{
constexpr string_storage<2> string("Hi" + 2);
std::cout << c_str(string) << '\n';
}
But it works.
Works, but the type is dependent on the string length. That's not feasible for cttti::type_id_t
, unless we find a way to type erase string_storage
at compile time.
Isn't there the same issue with a vanilla array?
No, I was using fixed size array for string storage. I have a erased_string_storage
based on yours almost ready, just some weird MSVC errors.
Have you checked if c_str()
is evaluated at compile time? My MSVC says:
'const ctti::detail::string_storage<0> &': type not allowed for constexpr function.
Seems like the inheritance trick is not allowed, i.e. working with compile-time references instead of values.
Arg, no. Damn.
Lambdas cannot be used in constexpr
expressions :(
template<char... Chars>
struct string_storage
{
static constexpr const char* c_str()
{
return &value[0];
}
static constexpr const char value[] = { Chars..., '\0' };
};
template<std::size_t... Is, typename F>
constexpr auto apply(F&& f, std::index_sequence<Is...>)
{
return f(Is...);
}
#define STRING(str) apply([](auto... Is) \
{ \
return string_storage<str[Is]...>{}; \
}, std::make_index_sequence<sizeof(str)-1>{})
const char* str = STRING("hello")).c_str();
Ok I give up. Let's use sprout::string
.
But it also uses a character array internally and conditional compilation ensures that no constexpr will be used on MSVC as far as I can see.
See https://github.com/Manu343726/ctti/commit/ecb4a37e9a2244c13249ec513256ef945335a339
I have successfully implemented ctti::detail::array
on MinGW GCC 5.1, and ctti::detail::string
using it as storage. Since from the three (four including MinGW) compilers, VS is the last worrying me, I have thought about a solution.
The main problem here is to provide a null-terminated c_str()
member for ctti::detail::string
. We could split string implementation into two variants:
const char*
based impl, with static storage string in c_str()
. This drops constexpr
support, but at least it's null-terminated and initialized only once during first call. This would be the implementation for MSVC until they implement constexpr
array initialization. I have added an #error
message at top of ctti::detail::array
header.constexpr
array as that commit shows. Guaranteed constexpr
null-terminated string for every string and substring.I never liked the macro trickery needed for implementing string literal -> string template
transformation, but seems like @irrequietus did the work for us :) https://github.com/irrequietus/typestring
We would need to try it with Visual Studio first, but this may be the solution to all our problems.
@Manu343726 thanks for the interest, I am doing some benchmark code right now because this is actually quite a fast way for generating the necessary boilerplate because of the hexadecimal arithmetic progression. The code is 100% C++11/14 standard compliant but I am not in front of a system with Visual Studio right now to test it. clang++
and g++
do just fine, with g++
being actually able to handle extremes I haven't even coded into typestring.hh
yet. I would be honestly very interested to hear from you guys and will be glad to be of help on this should you need me.
Does your macro require a string literal as argument? Since we have static char array and so far the macro techniques for converting this I have seen require string literals.
As far I know __PRETTY__FUNCTION__
is treated as a string literal, so that shouldn't be a problem. We just have to discard all the constexpr
array/string work and compute things with tmp.
No, it is a char array, sadly: https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html
@foonathan @Manu343726 It will use a string literal in situ for creating the actual type parameter, essentially this code is valid:
template<typename T = typestring_is("hello")> // irqus::typestring<'h','e','l','l','o'>
struct some_class_template {};
typedef is_typestring("hello") mytype; // contains accessible static constexpr array.
Note that within the resulting type, you will also have a static constexpr array of the string literal used so it is a simple design working both ways if needed. It can also be made to work with constexpr strings in a seamless manner should there be need. The string literal is essentially converted into a char array during the transformation, in situ.
Sadly this doesn't work for us :( It was a great alternative to get rid from all the constexpr
string details.
@Manu343726 @foonathan the following code works in an as-is basis, if you give me more insight as to what you want exactly to be done more, I will be happy to investigate and provide:
#include "typestring.hh"
#include <type_traits>
#include <iostream>
static constexpr char const array[] = { 'a','b','c','\0' };
int main() {
std::cout << std::boolalpha
<< std::is_same< typestring_is("abc")
, typestring_is(array)>::value
<< std::endl;
return {};
}
int main()
{
std::cout << typestring_is(__PRETTY_FUNCTION__).data() << "\n";
}
This works on Clang, but g++ discards __PRETTY_FUNCTION__
as typestring_is()
arg for not being constexpr
.
I know how we can cache the string! :D Variable templates!
template <typename T>
constexpr string get_type_name() {...}
template <typename T>
constexpr string type_name = get_type_name<T>();
And type_id_t can now store a pointer to string
. This is so obvious, why haven't we thought about this earlier?
Great! I have updated my VS to 2015 release and rewrote array initialization. It's finally working.
@Manu343726 @foonathan Essentially as reported in issue #5 at 5 -129167070. __PRETTY_FUNCTION__
is neither constexpr nor a string literal but it is a non-standard extension built upon the standard __func__
which is a static local const variable. If we want to be really pedantic, anything defined as such cannot be used in the context of integral constant expressions in the realm of constexpr metaprogramming or as char-typed non-type parameter types in a template parameter list for template metaprogramming purposes.
I have also seen that you are using __FUNCSIG__
for Microsoft compilers which according to their specification is actually a string literal. Thus, in implementing ctti
, you are dealing with wildly different things in different compilers.
Relevant links for __func__
, __PRETTY_FUNCTION__
interpretation by the implementors (I believe the last reply in the second link is yours) :
__func__
, __FUNCTION__
& __PRETTY_FUNCTION__
as constexprWhat typestring_is
does is to yield a type alla decltype by design, instantiating a class template with char-typed non-type arguments in its list. This means that whatever it accepts must be a true constant expression for what concerns the translation unit involved. To my surprise for clang++, this passes as ok in clang++ 3.6.1 a GNU/Linux host with even -Wall -Wextra -Wpedantic asides -std=c++14 (while g++ 5.1.0 rejects them rightfully because of the nature of __func__
):
#include <iostream>
#include "typestring.hh"
/*
* Please remember that typestring_is returns a type, just like decltype returns
* one, so follow conventions stemming from that one when using :: and . for
* calls to static member functions like data().
*
* The following work in clang++ 3.6.1 stable with -std=c++14, despite the non-
* standard __PRETTY_FUNCTION__, __FUNCTION__ refer to the standard
* __func__ variable, which is a static local const and therefore not usable as
* constant expression for use in constexpr metaprogramming or for use as an
* argument to non-type template parameters.
*/
void checker() {
std::cout << typestring_is(__PRETTY_FUNCTION__)::data() << std::endl;l
std::cout << typestring_is(__PRETTY_FUNCTION__)().data() << std::endl;
std::cout << typestring_is(__FUNCTION__)::data() << std::endl;
std::cout << typestring_is(__FUNCTION__)().data() << std::endl;
std::cout << typestring_is(__func__)::data() << std::endl;
std::cout << typestring_is(__func__)().data() << std::endl;
}
int main() {
checker();
std::cout << typestring_is(__PRETTY_FUNCTION__)::data() << std::endl;
std::cout << typestring_is(__PRETTY_FUNCTION__)().data() << std::endl;
std::cout << typestring_is(__FUNCTION__)::data() << std::endl;
std::cout << typestring_is(__FUNCTION__)().data() << std::endl;
std::cout << typestring_is(__func__)::data() << std::endl;
std::cout << typestring_is(__func__)().data() << std::endl;
return {};
}
There is however one case where even the standard __func__
will comply to what you ask in all metaprogramming context, where both g++
and clang++
implementors have consensus on the behavior of such an identifier (will work with recent compilers and -std=c++14):
#include <cstdio>
constexpr char X() {
return __func__[0];
}
template<char C>
void check() {
printf("%c\n",C);
}
int main() {
check<X()>();
return {};
}
As for storing pointers to a string defined elsewhere with proper static(/+"constexpricity") linkage in the translation unit of interest, it is obvious that you are using an integral constant expression at this point, meaning it should be usable within the context of a non-type parameter. But you are not defining them in-situ :)
In conclusion, this isn't a typestring_is
issue (it works as advertised with anything that is a constexpr char array or a string literal and constexpr strings can be added if one wishes to), but one of liberal interpretation of the actual use of __func__
in compile-time expressions (regardless constexpr/template metaprogramming) by compiler implementors (in this case, clang++
since g++
makes the thing quite clear in its bug reports).
Exactly, that's why I was asking whether or not it works.
With anything that is a true integral constant expression, it does work exactly as advertised, yielding the typestring type; your use of __func__
needs workarounds to work in true constexpr/metaprogramming scenarios. It is interesting that they opted for different intepretations of the standard. The __FUNCSIG__
in VS 2015 is however a string literal according to Microsoft and you are using it in your code when compiling in Windows with it. Don't rely on VS. In conclusion, you cannot really rely on __PRETTY_FUNCTION__
either cross-compiler wise for true compile time as it is. How... nice of them. But it is not a problem related to typestring_is
or constexpr strings: both are "affected" by it.
Yes, I am not blaming your library. :)
It is better then everything else I have seen in this regard. Good job.
I'm with Jonathan, I would prefer to use your lib since I'm more confident of tmp based solutions than cutter edge constexpr
that is still a bit fragile. But since typestring_is()
cannot handle __PRETTY_FUNCTION__
and similar, we cannot use it :( That trick is the basis of this library.
This makes me sad since the objective of ctti was to provide simple compile-time type information, not wasting two weeks in rolling out and testing a full featured constexpr
string implementation.
Guys, your ideas guys are awesome; I am just surprised that they are letting the __func__
get such wild interpretations instead of what is being written in the standard. We could have easily had reflection if they really amended this properly. However, it is not that "constexpr string classes" can handle __PRETTY_FUNCTION__
either on-site and I wanted this to be clear, in true constant expression manner:
#include <iostream>
// there is NO doubt this is a constexpr string class
struct cxstring {
const char * data;
const int size;
template<int s>
constexpr cxstring(char const (&c)[s]) noexcept
: data(c), size(s)
{}
constexpr char operator[](int n) noexcept {
return data[n];
}
};
// there is no doubt that this template can only accept a constant expression
template<char C>
void checker() {
std::cout << C << std::endl;
}
// with -std=c++14
int main() {
// anything related to __func__ should fail because it isn't constexpr!
// so the constexpr constructor shouldn't accept it, g++ rejects!
// DESPITE being passed to a cxstring. Works in clang++ against standard?!?!?!
checker<cxstring("hello,world!")[0]>();
checker<cxstring(__func__)[0]>(); // this will work only in clang++ 3.6.1!!!
checker<cxstring(__PRETTY_FUNCTION__)[0]>(); // only in clang++ 3.6.1!!!
checker<cxstring(__FUNCTION__)[0]>(); // only in clang++ 3.6.1!!!
return {};
}
Whenever you are using proper linkage (static) + constexpr defined through a pointer, you can use typestring_is
and whatever else. In essence, you are always going to need a proxy for this and a constexpr string isn't going to be of much help without it if you are planning to use compile-time reflection through such a venue. That's an interesting problem alright.
And many thanks for the interest, I truly appreciate it! Good luck with ctti
!
Current discussed alternatives:
in favor of
ctti::detail::string** features, maybe raising it out of the
detail` namespace. Pros: No implementation headaches, Cons: No user direct access to compile time computed data.ctti::detail::string
storage. Pros: Correct null-terminated sub strings. Cons: Compile-time performance (Strings should be copied always), maybe executable size bloat.