请考虑像这样的元函数
#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");
当单独以这种形式使用时,污染 Sum
感觉是多余的和 Product
具有 std::integral_constant
的结构.为了向您展示我们确实可以做到,请考虑以下几点:
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
可以保持没有定义,使它们变得平凡不可推断和不可构造,并且语法看起来也更清晰。现在,问题来了。如果我们希望我们所有的元函数都有一个统一的静态接口(interface)怎么办?也就是说,如果我们引入
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 使用的概念之一是 metafunction class .我们可以对它进行一些 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
使用两种不是同一基础类型的整数常量的类型会给您带来您想要的不完整类型错误。
使用元函数类还为您提供了柯里化(Currying)的优势。您不能真正从元函数“返回”一个类模板,但是您可以返回一个元函数类:
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++ - 如何通过未定义类型定义元函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31761965/