c++ 非推导上下文中模板引元推导

wztqucjr  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(122)

下面的(简化)代码无法编译,因为编译器无法推导模板参数。

#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;
}

错误消息为

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));

显然,这是一个nondeduced context的案例。
显然,减少模板参数的“嵌套”可以解决这个问题。例如,print例程可以修改为

template <typename U>
void print(std::vector<U>&& vec)
{
  // which animal?
  for (const auto& x : vec)
    std::cout << x << std::endl;
  vec.resize(0);
}

然而,该解决方案不是最优的,因为

  • print可以潜在地用std::vector示例化,该类型不是mytype中给出的选择之一
  • 在实际的代码中,我可能需要知道实际的模板参数animal a

假设我们在第二个代码块中使用print,那么如何:

  • 将示例化限制为允许的类型mytype<a>,其中aanimal
  • 推导模板参数animal

或者有更好的解决方案,以不同的方式实现print
请注意,我正在寻找一个可扩展的解决方案。显然,对于上面的例子,解决方案很容易,只要考虑intdouble。相反,我正在寻找一种解决方案,即使在enum class animal和/或类型别名mytype被更改时,我也可以通用。另外,我正在寻找C++17标准中的解决方案。

qlvxas9a

qlvxas9a1#

如果你能在枚举中创建枚举数列表的参数包,你可以做各种各样的事情:

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>;

一旦你有了这个参数,你就可以解压缩参数包,并使用fold表达式来检查哪个mytype示例化对应于特定类型:

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);

您可以执行类似的操作,检查mytype的任何示例化是否具有特定类型(换句话说,如果T在列表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{});
}

在static_assert中使用这个来限制可以传递给print的类型:

template <typename U>
void print(std::vector<U>&& vec)
{
    static_assert(is_allowed<U>());
    animal a = animal_from_type<U>();
}

Demo

相关问题