如何通过未定义的类型定义元函数
How to define metafunctions by undefined types?
请考虑像
这样的元函数#include <type_traits>
template <typename T, T N, T M>
struct Sum : std::integral_constant <T, N + M> {};
template <typename T, T N, T M>
struct Product : std::integral_constant <T, N * M> {};
它们的结果可以通过::value
成员提取:
static_assert (Sum <int, 3, 4>::value == 7, "3 + 4 == 7");
static_assert (Product <int, 2, 5>::value == 10, "2 * 5 == 10");
两个元函数都有类似的静态签名。也就是说,它们将T
与每一对T
相关联,其中T
受到与std::integral_constant
施加的限制相同的限制,并且可以是可和的或可乘的。因此,我们可以创建一个泛型元函数来执行计算。
template <typename T, template <typename U, U, U> class F, T N, T M>
struct EvaluateBinaryOperator : std::integral_constant <T, F <T, N, M>::value> {};
static_assert (EvaluateBinaryOperator <int, Sum, 3, 4>::value == 7, "3 + 4 == 7");
static_assert (EvaluateBinaryOperator <int, Product, 2, 5>::value == 10, "2 * 5 == 10");
当单独以这种形式使用时,用std::integral_constant
的结构污染Sum
和Product
感觉是多余的。为了表明我们确实可以不需要,请考虑以下事项:
template <typename T, T N, T M, T R = N + M>
struct Sum;
template <typename T, T N, T M, T R = N * M>
struct Product;
template <typename> struct EvaluateBinaryOperator;
template <typename T, template <typename U, U, U, U> class F, T N, T M, T R>
struct EvaluateBinaryOperator <F <T, N, M, R> > : std::integral_constant <T, R> {};
static_assert (EvaluateBinaryOperator <Sum <int, 3, 4> >::value == 7, "3 + 4 == 7");
static_assert (EvaluateBinaryOperator <Product <int, 2, 5> >::value == 10, "2 * 5 == 10");
不使用Sum
和Product
的成员,我们专门化一个默认实参,并只在EvaluateBinaryOperator
中提取它。作为一个额外的好处,Sum
和Product
可以保持没有定义,使它们变得非常不可推断和不可构造,语法看起来也干净得多。现在,问题来了。如果我们希望所有的元函数都有一个统一的静态接口呢?也就是说,如果我们引入
template <typename...> struct Tuple;
template <typename T, T> struct Value;
并要求我们所有的元函数看起来像template <typename> struct
?例如,
template <typename> struct Sum;
template <typename T, T N, T M>
struct Sum <Tuple <Value <T, N>, Value <T, M> > > :
std::integral_constant <T, N + M> {};
template <typename> struct Product;
template <typename T, T N, T M>
struct Product <Tuple <Value <T, N>, Value <T, M> > > :
std::integral_constant <T, N * M> {};
现在,我们想把它们转换成:
template <typename, typename> struct Sum;
template <typename T, T N, T M, typename R = Tuple <Value <T, N + M> > >
struct Sum <Tuple <Value <T, N>, Value <T, M> >, R>;
template <typename, typename> struct Product;
template <typename T, T N, T M, typename R = Tuple <Value <T, N * M> > >
struct Product <Tuple <Value <T, N>, Value <T, M> >, R>;
使得我们可以用
提取值template <typename> struct Evaluate;
template <template <typename, typename> class F, typename I, typename O>
struct Evaluate <F <I, O> > {
typedef O Type;
};
static_assert (std::is_same <
Evaluate <Sum <Tuple <Value <int, 3>, Value <int, 4> > > >::Type,
Tuple <Value <int, 7> >
>::value, "3 + 4 == 7");
static_assert (std::is_same <
Evaluate <Product <Tuple <Value <int, 2>, Value <int, 5> > > >::Type,
Tuple <Value <int, 10> >
>::value, "2 * 5 == 10");
熟悉c++标准的人会立即指出14.5.5/8:"专门化的模板形参列表不应包含默认的模板实参值。",并附有戏弄的脚注:"它们不可能被使用。"实际上,给任何现代编译器提供这段代码都会在Sum
和Product
模板专门化上产生关于违反标准的编译器错误。除了证明上述脚注缺乏作者的想象力;我们已经为它们创建了一个有效的用例。
我的问题现在可以提出:是否有其他方法可以实现类似的效果,其中Sum
和Product
仍然是未定义/不完整类型,从而平凡地不可推断和不可构造,同时仍然承担执行操作的责任?欢迎提出任何建议。
元编程是复杂的。这并不是语言的设计特征,而是人们发现的。因此,很难做到正确——因此人们提出了如何调用元函数的约定,以便为其他程序员提供理解代码的指南。最重要的约定之一是,要获得元函数的结果,您可以这样做:
typename metafunc<some_args...>::type
您关于编写Sum
的各种建议根本不符合这个惯例,我认为即使是许多非常有经验的模板元程序员也很难跟上您的做法——甚至依赖于对模板部分专门化如何工作的规则的更改。然而,改变这些规则根本不是一个令人信服的例子。让我提出更好的建议。
类型是一等公民。一切都与类型有关。值和模板模板则不是。它们充其量只是笨拙。您需要编写Sum<int, 1, 2>
的事实很糟糕。此外,当您必须允许具有不同数量参数的值或模板模板时,基本上不可能编写泛型元函数。让我们试着把所有东西都归类。
是Boost。MPL使用的是一个元函数类。我们可以对它进行一点c++ 11化,并将元函数类定义为如下的类:
struct C {
template <typename... Args> // doesn't have to be variadic
using apply = /* whatever */;
};
使用这个想法,我们可以说EvaluateBinaryOperator
看起来像:
template <typename Op, typename A1, typename A2>
struct EvaluateBinaryOperator {
using type = typename Op::template apply<A1, A2>;
};
请注意,当一切都是类型时,它是多么干净!当然,调用apply
的语法并不像steller那样复杂,但这非常简单。事实上,我们可以归纳为:
template <typename Op, typename... Args>
struct EvaluateOperator {
using type = typename Op::template apply<Args...>;
};
容易。现在让我们回到Sum
。类型是一等公民,所以它不再接受值。它需要类型。但是我们仍然可以强制这些类型是具有相同类型的整型常量:
class Sum {
template <typename, typename >
struct impl;
template <typename T, T a, T b>
struct impl<std::integral_constant<T, a>,
std::integral_constant<T, b>>
{
using type = std::integral_constant<T, a+b>;
};
public:
template <typename T, typename U>
using apply = typename impl<T, U>::type;
};
这符合元函数类的模型,所以我们可以在EvaluateOperator
中使用它,例如:
std::cout << EvaluateOperator<Sum,
std::integral_constant<int, 1>,
std::integral_constant<int, 2>
>::type::value << std::endl; // prints 3
用两个不是相同底层类型的整型常量的类型实例化impl
会给你想要的不完整类型错误。
使用元函数类还为您提供了能够进行curry的优势。你不能真正地从元函数中"返回"一个类模板,但是你可以返回一个元函数类:
template <typename Op, typename... Args>
struct curry {
struct type {
template <typename... OtherArgs>
using apply = typename EvaluateOperator<Op, Args..., OtherArgs...>::type;
};
};
using Add1 = curry<Sum, std::integral_constant<int, 1>>::type;
std::cout << Add1::apply<
std::integral_constant<int, 5>
>::type::value << std::endl; // prints 6
- 在命名空间中定义函数还是限定函数
- 为什么在定义函数之前先声明它
- 使用用户定义函数的字符串反转
- 用户定义函数中的指针和输入
- 这个c++代码是如何在没有定义函数的情况下运行的
- 具有外部"c"和程序集的未定义函数
- 已定义函数时出现 G++ "未定义的引用"错误
- 将自定义函数传递到基抽象类中以延迟执行
- C++使用 rand 定义函数语法
- Arduino:在 loop() 和自定义函数中运行相同的代码时出现问题
- 将具有固定签名的自定义函数名称注入 CRTP
- 使用定义函数模板别名
- 是否可以使用单个定义定义函数的常量和常规版本?(使用模板,自动,decltype等)
- 在内联程序集中定义函数和从 C++ 调用时出现问题
- 在 Metal 着色器代码中,如何定义函数的 in/out 参数变量?
- 声明和定义函数静态会产生"undefined reference to function_name()"
- 朋友定义函数的名称空间是什么
- 介子 对用户定义函数的未定义引用
- 如何使用 "using" 关键字定义函数原型/签名
- 在 C 结构中定义C++函数