Manu343726 / ctti

Compile Time Type Information for C++
MIT License
569 stars 55 forks source link

Alternatives for null-terminated substrings #5

Open Manu343726 opened 9 years ago

Manu343726 commented 9 years ago

Current discussed alternatives:

Manu343726 commented 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.

foonathan commented 9 years ago

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. :(

Manu343726 commented 9 years ago

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...

foonathan commented 9 years ago

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.

Manu343726 commented 9 years ago

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.

foonathan commented 9 years ago

Isn't there the same issue with a vanilla array?

Manu343726 commented 9 years ago

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.

Manu343726 commented 9 years ago

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.

foonathan commented 9 years ago

Arg, no. Damn.

Manu343726 commented 9 years ago

Lambdas cannot be used in constexprexpressions :(

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();
Manu343726 commented 9 years ago

Ok I give up. Let's use sprout::string.

foonathan commented 9 years ago

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.

Manu343726 commented 9 years ago

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:

Manu343726 commented 9 years ago

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.

irrequietus commented 9 years ago

@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.

foonathan commented 9 years ago

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.

Manu343726 commented 9 years ago

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.

foonathan commented 9 years ago

No, it is a char array, sadly: https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html

irrequietus commented 9 years ago

@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.

Manu343726 commented 9 years ago

Sadly this doesn't work for us :( It was a great alternative to get rid from all the constexpr string details.

irrequietus commented 9 years ago

@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 {};
}
Manu343726 commented 9 years ago
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.

foonathan commented 9 years ago

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?

Manu343726 commented 9 years ago

Great! I have updated my VS to 2015 release and rewrote array initialization. It's finally working.

irrequietus commented 9 years ago

@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) :

What 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).

foonathan commented 9 years ago

Exactly, that's why I was asking whether or not it works.

irrequietus commented 9 years ago

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.

foonathan commented 9 years ago

Yes, I am not blaming your library. :)

It is better then everything else I have seen in this regard. Good job.

Manu343726 commented 9 years ago

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.

irrequietus commented 9 years ago

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.

irrequietus commented 9 years ago

And many thanks for the interest, I truly appreciate it! Good luck with ctti!