Search code examples
c++templatesc++17clang++

Why instantiation of my template with non-type template parameter leads to compilation error if instantiated with reference?


I have value_list struct template:

template <decltype(auto)... values>
struct value_list
{};

and I want to implement a method to be able to access template parameter by index. So I ended up with this solution:

#include <type_traits>

namespace details
{
// Second parameter T is just to get rid of errors like this:
// Ambiguous partial specializations of 'at_impl<0, value_list<1>>'
template <std::size_t I, typename T, typename ValueList>
struct at_impl;

template <decltype(auto) Head, decltype(auto)... Rest>
struct at_impl<0, void, value_list<Head, Rest...>>
{
    static constexpr decltype(auto) value = Head;
};

template <std::size_t I, decltype(auto) Head, decltype(auto)... Rest>
struct at_impl<I, int, value_list<Head, Rest...>>
{
    static constexpr decltype(auto) value = at_impl<I-1, std::conditional_t<I-1 == 0, void, int>, value_list<Rest...>>::value;
};
}  // namespace details

template <std::size_t I, typename ValueList>
struct at
{
    static constexpr decltype(auto) value = details::at_impl<I, std::conditional_t<I == 0, void, int>, ValueList>::value;
};

template <std::size_t I, typename ValueList>
inline constexpr decltype(auto) at_v = at<I, ValueList>::value;

which can be used like this:

#include <iostream>

#define LOG(x) \
std::cout << #x << ": " << x << '\n'

int main()
{
    static constexpr int i = 5;
    const std::size_t index = 0;
    LOG((at_v<index, value_list<i>>));
    
    return 0;
}

output: (at_v<index, value_list<i>>): 5

But the problem is that it does not compile if we instantiate value_list template with reference as argument:

int main()
{
    static constexpr int i = 5;
    const std::size_t index = 0;

    constexpr const int & ri = i;
    LOG((at_v<index, value_list<ri>>));
    
    return 0;
}

error: implicit instantiation of undefined template 'details::at_impl<0, void, value_list<i>>'

Why at_impl does not compile if value_list contains reference and how to fix this?

Here is link to wanbox if you wish to play with my code.


Solution

  • Well, it seems that at least I figured out how to fix my code to make it work identically under all three major compilers(msvc, gcc and clang). But template at in this case should be inside value_list:

    #include <tuple>
    #include <type_traits>
    
    template <decltype(auto)... Values>
    struct value_list
    {
        template <std::size_t I>
        using at_t = std::tuple_element_t<I, std::tuple<decltype(Values)...>>;
    
        template <std::size_t I>
        static constexpr at_t<I> at_v = std::get<I>(std::tuple<decltype(Values)...>(Values...));
    };
    
    int main()
    {
        static constexpr int i = 5;
        constexpr const int & ri = i;
        
        using list = value_list<'t', ri, i, 10>;
        
        static_assert(std::is_same_v<decltype(list::at_v<0>), const char>);
        static_assert(std::is_same_v<decltype(list::at_v<1>), const int &>);
        static_assert(std::is_same_v<decltype(list::at_v<2>), const int>);
        static_assert(std::is_same_v<decltype(list::at_v<3>), const int>);
        static_assert(list::at_v<0> == 't');
        static_assert(list::at_v<1> == 5);
        static_assert(list::at_v<2> == 5);
        static_assert(list::at_v<3> == 10);
        static_assert(std::is_same_v<list::at_t<0>, char>);
        static_assert(std::is_same_v<list::at_t<1>, const int &>);
        static_assert(std::is_same_v<list::at_t<2>, int>);
        static_assert(std::is_same_v<list::at_t<3>, int>);
        
        return 0;
    }
    

    I have checked that this code successfully compiles by:

    • x64 msvc v19.38
    • AppleClang 14.0.0.14000029
    • GCC 12.3.0

    Concerning to "why" code provided in my question does not compile I don't have better answer than "unlike gcc and msvc for some reason clang++ cannot compile this code".

    As side note: After reading one of the Sam's answers provided by @TedLyngmo in comment section I still do not get why static_assert(std::is_same_v<list::at_t<2>, int>); got compiled? Because parameter with index 2 is i which is unparenthesized id-expression(am I wrong?) then according to the C++17 standard decltype(i) should be type of the entity named by i. And as we can clearly see that type is const int(not int) because constexpr implies const. But in the same time I doubt that all three major compilers can be wrong. Rather I don't take something into account and this is subject for another question.