Search code examples
c++metaprogrammingtype-traitsis-same

std::is_same doesn't work through decltype of constexpr auto variable


I was trying to static_assert that some meta transformer algorithm worked, and it incredibly did not compare to same, even though the typeid().name() returned the exact same string.

A repetition of the type expression in the typedef could fix the is_same, but I can't understand how repeating the initializer expression in the typedef changes the type, over taking the decltype of an auto variable initialized with that same expression.

A more concrete explanation of what I was doing:
I did a meta transformer that can transform a meta value list (containing enumerators) to a std::tuple of all enumerators. Then I checked that the tuple generated was the one I expected.

all code hereunder, check the line with the comments // works // doesn't work
And the final test is at the end.

#include <type_traits>
#include <tuple>

template<typename T> struct ValueListAsTuple;  // master template
// destructurer of list:
template<template <auto...> class L, auto... Vs>
struct ValueListAsTuple<L<Vs...>>
{
    static constexpr auto value = std::make_tuple(Vs...);
    //using type = decltype(std::make_tuple(Vs...)); // works
    using type = decltype(value); // doesn't work
};

// template typedef
template<typename T> using ValueListAsTuple_t = typename ValueListAsTuple<T>::type;

struct Kind
{
    enum EnumType { E1, E2 };
    template <auto... Values> struct MetaVals{};  // meta value list
    using MetaValueList = MetaVals<
        E1,
        E2
    >;
};

int main(int argc, const char* argv[])
{
    auto tuple = ValueListAsTuple_t< Kind::MetaValueList > {};

    //std::cout << typeid(tuple).name() << '\n';
    // this prints: "class std::tuple<enum Kind::EnumType, enum Kind::EnumType>"

    // manual re-creation of the type, for testing:
    std::tuple< Kind::EnumType, Kind::EnumType > t2;

    //std::cout << typeid(t2).name() << '\n';
    // this prints the exact same thing.

    static_assert( std::is_same_v< std::tuple_element<0, decltype(tuple)>::type, Kind::EnumType > );
    static_assert( std::is_same_v< std::tuple_element<1, decltype(tuple)>::type, Kind::EnumType > );

    // WHAT ???
    static_assert( std::is_same_v< 
                        ValueListAsTuple_t< Kind::MetaValueList >,
                        std::tuple< Kind::EnumType, Kind::EnumType >
                                 > );
    // is_convertible_v works but I don't care ! wth ?
}

So as you can see, is_same could not deduce the same type when the type was declared through value but it worked if I repeat std::make_tuple(... I can't see where there is any difference between the 2 forms.

I even checked element by element that the types are the same at each index of the tuple.

Check that in action in godbolt:
https://godbolt.org/z/QUCXMB

The behavior is the same across gcc/clang/msvc


Solution

  • It's a trivial error, really, but hard to spot unfortunately. The issue you face is because constexpr implies const. So in your example type is const tuple<...>, which is not the same type as the non cv-qualified type you check. A short fix to the alias should make your test pass:

    using type = std::remove_const_t<decltype(value)>;