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

Viewed 60

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?

1 Answers

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

Related