【问题标题】:Generic way for template argument checks模板参数检查的通用方法
【发布时间】:2021-12-26 13:30:18
【问题描述】:

我必须遵循以下程序:

#include <iostream>

#define max_dim 100

template<unsigned IDim>
struct Node{};

int main() {
    uint x;
    std::cout << "Input dimension: ";
    std::cin >> x;
    if(x < max_dim){
        Node<x> node;
    }else{
        std::cout << "False Input.";
    }
}

无法编译,因为表达式Node&lt;x&gt; node 需要x 是一个常量值,而事实并非如此。因为x 的可能性有限(受max_dim 限制)我当然可以只使用if 条件或switch 语句,但这会导致大量冗余代码(尤其是因为我正在为每个节点做一堆操作)。

我并不能真正改变节点的结构。

有没有更优雅的方法来实现这一点??

【问题讨论】:

  • 无论您如何编写代码,问题始终是所有将Node&lt;x&gt; 用于x 的所有可能值的函数都需要实例化。这将花费大量的编译时间,并且可能会导致一个大的二进制文件。你同意吗?
  • 您到底想要达到什么目的?这感觉像是一个 XY 问题。那个类Node 无论如何都没有状态,也没有方法。
  • @user17732522 无论如何,这是一个巨大的项目,目前有 1000 种方法。我认为编译时间不是问题。
  • @al3c 我尝试创建最小的示例,其中仅包含与我的问题相关的内容。在我巨大的实际代码中,Node当然有状态和方法。
  • @wittn 是否可以接受 C++20 解决方案或您支持哪个版本?

标签: c++ templates


【解决方案1】:

std::index_sequence 可能会有所帮助。

然后有几种方法从运行时值中选择编译代码(递归,折叠表达式,对索引进行线性检查),这里使用数组(O(1) 访问,但在内存中是线性的)。

template <std::size_t I>
void f(/*..*/)
{
    Node<I> node;
    // Your code...
}

template <std::size_t... Is>
constexpr std::array<void(*)(/*..*/), sizeof...(Is)>
make_funcs(std::index_sequence<Is...>)
{
    return {{ &f<Is>... }};
}

template <std::size_t N>
constexpr std::array<void(*)(/*..*/), N> make_funcs()
{
    return make_funcs(std::make_index_sequence<N>());
}

然后

int main() {
    std::cout << "Input dimension: ";
    uint x;
    std::cin >> x;
    if (x < max_dim) {
        constexpr auto funcs = make_funcs<max_dim>();
        funcs[x](/*..*/);
    } else {
        std::cout << "False Input.";
    }
}

【讨论】:

  • 我喜欢这个解决方案。如果没有额外的功能,这是不可能做到的,对吧?
  • 我偶然发现了一个小问题。如果我需要返回节点,这是不可能的,因为节点具有不同的类型,所以数组必须包含具有不同返回值的函数,据我所知,这是不可能的。有没有办法解决这个问题?
  • 额外的函数可能会被 lambda 替换(特别是在 C++20 中,它允许指定 lambda 的模板)。
  • 一个函数只能返回一种类型(在编译时固定)。所以要么返回基类或其他类型擦除类型,要么返回所有可能类型的std::variant
【解决方案2】:

使用辅助函数的 C++17 解决方案,假设 Node&lt;x&gt; 上下文不需要返回值:

#include<utility>

template<typename T, auto... I>
void f(std::integer_sequence<T, I...>, T x) {
    ((x == I && ([]{
        Node<I> node;
        // do something with node
    }(), true)) || ... || (std::cout << "False Input."));
}
    
// in main
f(std::make_integer_sequence<decltype(x), max_dim>{}, x);

可以只使用 lambdas 而不是辅助函数来编写解决方案,但它看起来会更加不清晰。这里的重要概念是折叠表达式和将模板参数包引入函数的std::integer_sequence 方法。 &amp;&amp;/||/(..., true) 构造本质上只是一个 if/else if 构造,它被编写为依赖于逻辑运算符的短路行为的单个表达式。

【讨论】:

  • 这似乎适用于我的示例(我仍然有点困惑,为什么没有更多的本地方法可以做到这一点)。无论如何,在我的代码中,我需要将一个节点作为参数传递并将其转换为它的父类(IDim 模板对于两者都是相同的)。你能告诉我如何改变你的代码来完成这个吗?
  • @wittn 没有更直接的方法可以做到这一点,因为它很少是人们想要做的。正如我所说,它将有效地将编译时间和二进制大小乘以 100 倍(假设您没有遇到编译器的实现限制)。通常对于这样的事情,会创建一个具有动态IDim 的类,而不是将其作为模板参数传递,或者使用虚拟继承来实现运行时多态性。我真的不明白你的问题。所有Node&lt;IDim&gt; 都继承自同一个基类吗?用更多细节修改问题可能会更好。
  • @wittn:您可以将任何代码放入 lambda,作为基类的转换节点。
猜你喜欢
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-13
相关资源
最近更新 更多