Search code examples
c++templatesautospecialization

What is the correct specialization for distinguishing between value template class and lvalue template class?


I need to make a template specialization which distinguish between a template class with (only) value parameters like the following:

template<auto ... __vz>
struct values{};

and a template class with (only) reference parameters like the following:

template<auto& ... __lvz>
struct lvalues{};

But with following specialization I was not successful:

template<typename>
struct is_values{
    static constexpr bool value = false;
};
template<template<auto ...>class __m, auto ... __vz>
struct is_values<__m<__vz ...> >{
    static constexpr bool value = true;
};

template<typename>
struct is_lvalues{
    static constexpr bool value = false;
};
template<template<auto& ...>class __m, auto& ... __vz>
struct is_lvalues<__m<__vz ...> >{
    static constexpr bool value = true;
};

When they are tested like the following:

const int a = 1;
const int b = 2;
const int c = 3;

int main()
{
    std::cout << is_values<lvalues<a,b,c> >::value <<std::endl; // wrong result
    std::cout << is_lvalues<values<1,2,3> >::value <<std::endl; // causes error
}

One of them gives a wrong result and one gives the following error:

test.cpp: In instantiation of ‘struct is_lvalues<values<1, 2, 3> >’:
test.cpp:51:41:   required from here
test.cpp:32:34: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
   32 | struct is_lvalues<__m<__vz ...> >{
      |                                  ^
test.cpp:32:34: error: could not convert ‘1’ from ‘int’ to ‘int&’
test.cpp:32:34: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:32:34: error: could not convert ‘2’ from ‘int’ to ‘int&’
test.cpp:32:34: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:32:34: error: could not convert ‘3’ from ‘int’ to ‘int&’
test.cpp:33:24: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
   33 |  static constexpr bool value = true;
      |                        ^~~~~
test.cpp:33:24: error: could not convert ‘1’ from ‘int’ to ‘int&’
test.cpp:33:24: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:33:24: error: could not convert ‘2’ from ‘int’ to ‘int&’
test.cpp:33:24: error: initializing ‘int&’ with ‘int’ in converted constant expression does not bind directly
test.cpp:33:24: error: could not convert ‘3’ from ‘int’ to ‘int&’
test.cpp: In function ‘int main()’:
test.cpp:51:43: error: ‘value’ is not a member of ‘is_lvalues<values<1, 2, 3> >’
   51 |  std::cout << is_lvalues<values<1,2,3> >::value <<std::endl; // causes error
      |                                           ^~~~~
make: *** [src/subdir.mk:20: src/test.o] Error 1
"make all" terminated with exit code 2. Build might be incomplete.

12:14:37 Build Failed. 14 errors, 0 warnings. (took 677ms)

So far I was only successful with the following specializations:

template<auto ... __vz>
struct is_values<values<__vz ...> >{
    static constexpr bool value = true;
};
template<auto& ... __vz>
struct is_lvalues<lvalues<__vz ...> >{
    static constexpr bool value = true;
};

But these are not desired because they only detect the types resulted from values and lvalues not any other similar template with different name.

So what is the right specialization?


Solution

  • If you're solving this for types, you can use std::is_lvalue_reference to do this for a single type. Since you seem to be using C++17, you can use fold expressions to apply this to a parameter pack:

    template <typename... Ts>
    using are_types_lvalues = std::integral_constant<bool, (std::is_lvalue_reference_v<Ts> && ...)>;
    
    template <typename... Ts>
    using are_types_values = std::integral_constant<bool, !(std::is_reference_v<Ts> || ...)>;
    

    If you can test the types, you can just use decltype to get a non-type parameter's type, and not worry about forcing an argument to bind to auto or auto&...

    template<typename>
    struct are_values : std::false_type {};
    template<template<auto ...>class __m, auto ... __vz>
    struct are_values<__m<__vz ...>> : std::integral_constant<bool, !(std::is_reference_v<decltype(__vz)> || ...)> {};
    
    template<typename>
    struct are_lvalues : std::false_type {};
    template<template<auto ...>class __m, auto ... __vz>
    struct are_lvalues<__m<__vz ...>> : std::integral_constant<bool, (std::is_lvalue_reference_v<decltype(__vz)> && ...)> {};
    

    usage:

    template<auto... __vz>
    struct values{};
    
    template<auto& ... __lvz>
    struct lvalues{};
    
    const int a = 1;
    const int b = 2;
    const int c = 3;
    
    static_assert(are_values<values<1,2,3>>::value, "");
    static_assert(!are_values<lvalues<a,b,c>>::value, "");
    static_assert(!are_lvalues<values<1,2,3>>::value, "");
    static_assert(are_lvalues<lvalues<a,b,c>>::value, "");
    

    demo: https://godbolt.org/z/KTh9Wj