samedi 1 août 2015

How to define metafunctions by undefined types?

Please consider metafunctions like

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

Their result can be extracted through the ::value member:

static_assert (Sum <int, 3, 4>::value == 7, "3 + 4 == 7");
static_assert (Product <int, 2, 5>::value == 10, "2 * 5 == 10");

Both metafunctions have a similar static signature. That is, they associate a T to every pair of T's where T is subject to the same restrictions as those imposed by std::integral_constant and either being summable or multipliable. So we can create a generic metafunction to do the evaluation.

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

When used solely in this form, it feels redundant to to pollute Sum and Product with the structure of an std::integral_constant. To show you that we can do without indeed, please consider the following:

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

Instead of using members of Sum and Product, we specialize on a default argument and extract it only in EvaluateBinaryOperator. As an added bonus, Sum and Product can remain without definition, rendering them trivially non-inferrable and non-constructable and the syntax looks much cleaner too. Now, here's the catch. What if we would like all our metafunctions to have a uniform static interface? That is, what if we introduce

template <typename...> struct Tuple;

template <typename T, T> struct Value;

and require all our metafunctions to look like template <typename> struct? For instance,

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

Now, we would like to transform them to something like:

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

Such that we can extract values with

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>
>, "3 + 4 == 7");
static_assert (std::is_same <
    Evaluate <Product <Tuple <Value <int, 2>, Value <int, 5> > > >::Type
    Tuple <Value <int, 10>
>, "2 * 5 == 10");

Those of you familiar with the C++ standard will immediately point to 14.5.5/8: "The template parameter list of a specialization shall not contain default template argument values.", accompanied by the teasing footnote: "There is no way in which they could be used.". Indeed, feeding just about any modern compiler this code yields a compiler error on the Sum and Product template specializations about violation of the standard. Apart from proving the aforementioned footnote to lack the imagination of the author; we've created ourselves a valid use case for them.

My question can now be put: Are there any other ways to achieve a similar effect where Sum and Product remain undefined / incomplete types, thereby trivially being non-inferrable and non-constructable, while still carrying responsibility for performing the operation? Any suggestions are welcome. Thanks in advance.

Aucun commentaire:

Enregistrer un commentaire