如何通过未定义的类型定义元函数?

时间:2015-08-01 12:42:18

标签: c++ c++11 default-value template-specialization template-meta-programming

请考虑像

这样的元功能
#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的结构污染Productstd::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");

我们不是使用SumProduct的成员,而是专注于默认参数,并仅在EvaluateBinaryOperator中提取它。作为一个额外的好处,SumProduct可以保持不定义,使它们简单地不可推断和不可构造,语法看起来也更清晰。现在,抓住了。如果我们希望所有元函数都具有统一的静态接口,该怎么办?也就是说,如果我们介绍

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:&#34;专业化的模板参数列表不应包含默认模板参数值。&#34;,伴随着戏弄脚注:&#34;没有办法可以使用它们。&#34;。实际上,只需要提供任何现代编译器,此代码就SumProduct模板专业化产生了有关违反标准的编译器错误。除了证明上述脚注缺乏作者的想象力之外;我们为自己创建了一个有效的用例。

现在可以提出我的问题:是否有其他方法可以实现类似的效果,其中SumProduct仍然是未定义/不完整的类型,因此通常是不可推断和不可构造的,而还负责执行这项行动?欢迎任何建议。提前谢谢。

1 个答案:

答案 0 :(得分:4)

元编程很复杂。它不是该语言的设计特征,它被发现了。因此,做起来很难 - 所以人们想出了如何调用元函数的约定,所以给其他程序员指导理解代码。其中一个最重要的约定是,要获得元函数的结果,你可以这样做:

typename metafunc<some_args...>::type

你撰写Sum的各种提案完全不符合这个惯例,我认为即使是很多非常有经验的模板元编程人员也很难跟踪你所做的事情 - 甚至会依赖于变化模板部分专业化的工作原则。尽管如此,改变这些规则并不是一个令人信服的例子。让我改为提出更好的建议。

元编程中的类型是一等公民。一切都适用于类型。值和模板模板不是。他们充其量只是笨重。你需要写Sum<int, 1, 2>很糟糕的事实。此外,当您必须允许不同数量的参数的值或模板模板时,基本上不可能编写通用元函数。因此,让我们尝试将所有内容保持为类型。

Boost.MPL使用的一个概念是metafunction class。我们可以使用C ++ 11-ify,并说一个元函数类是一个看起来像这样的类:

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的语法不是很好,但这很简单。事实上,我们可以概括为:

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将为您提供所需的不完整类型错误。

使用元函数类还可以为您提供咖喱的优势。你不能真的&#34;返回&#34;元函数中的类模板,但可以返回元函数类:

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