The following (simplified) code does not compile because the compiler cannot deduce a template parameter.
#include <iostream>
#include <utility>
#include <vector>
enum class animal : size_t { cat, dog };
template <animal a, class... U>
using animal_select_t = typename std::tuple_element<static_cast<size_t>(a), std::tuple<U...>>::type;
template <animal a>
using mytype = animal_select_t<a, int, double>;
template <animal a>
void print(std::vector<mytype<a>>&& vec)
{
for (const auto& x : vec)
std::cout << x << std::endl;
vec.resize(0);
}
int main()
{
std::vector<int> v(3, 1);
print(std::move(v));
std::cout << v.size() << std::endl;
return 0;
}
The error message is
deduce.cpp: In function ‘int main()’:
deduce.cpp:24:8: error: no matching function for call to ‘print(std::remove_reference<std::vector<int>&>::type)’
24 | print(std::move(v));
| ~~~~~^~~~~~~~~~~~~~
deduce.cpp:14:6: note: candidate: ‘template<animal a> void print(std::vector<typename std::tuple_element<static_cast<long unsigned int>(a), std::tuple<int, double> >::type>&&)’
14 | void print(std::vector<mytype<a>>&& vec)
| ^~~~~
deduce.cpp:14:6: note: template argument deduction/substitution failed:
deduce.cpp:24:8: note: couldn’t deduce template parameter ‘a’
24 | print(std::move(v));
Apparently, this is a case of a nondeduced context.
Obviously, reducing the "nesting" of template parameters solves the issue. For example, the print
routine can be modified as
template <typename U>
void print(std::vector<U>&& vec)
{
// which animal?
for (const auto& x : vec)
std::cout << x << std::endl;
vec.resize(0);
}
However, this solution is not optimal because
print
can be potentially instantiated with a std::vector
of a type which is not one of the choice given in mytype
In an actual code, I may need to know the actual template parameter animal a
.
Assuming we use print
as in the second block of code, how can one:
restrict the instantiation to the allowed types, mytype<a>
, with a
an animal
?
deduce the template parameter animal
?
Or is there a better solution, implementing differently print
?
Please notice that I am looking for a scalable solution.
Obviously for the example above, the solution is easy, given that only int
and double
are considered.
I am instead looking for a solution that I can generically use even when the enum class animal
and/or the type alias mytype
is changed.
Also, I am looking for a solution in the c++17 standard.
You can do all sorts of things if you can make a parameter pack of the list of enumerators in your enum:
template <animal... as>
struct animal_list {};
// needs to be kept updated with the definition of the enum class animal
using all_animals = animal_list<animal::cat, animal::dog>;
Once you have that, you can unpack the parameter pack and use for example a fold expression to check which mytype
instantiation corresponds to a particular type:
template <typename T, template <animal> typename Selector, animal... as>
constexpr auto animal_from_type_helper(animal_list<as...> list) -> animal {
animal a{};
static_cast<void>(((a = as, std::is_same_v<Selector<as>, T>) || ...));
return a;
}
template <typename T>
constexpr auto animal_from_type() -> animal {
return animal_from_type_helper<T, mytype>(all_animals{});
}
static_assert(animal_from_type<int>() == animal::cat);
static_assert(animal_from_type<double>() == animal::dog);
You can do something similar to check if any instantiation of mytype
has a particular type (in other words, if T
is in the list animal_select_t<a, int, double>
)
template <typename T, template <animal> typename Selector, animal... as>
constexpr bool is_allowed_helper(animal_list<as...> list) {
return (std::is_same_v<Selector<as>, T> || ...);
}
template <typename T>
constexpr bool is_allowed() {
return is_allowed_helper<T, mytype>(all_animals{});
}
Use this in a static_assert to restrict what types can be passed to print
:
template <typename U>
void print(std::vector<U>&& vec)
{
static_assert(is_allowed<U>());
animal a = animal_from_type<U>();
}