How to identify metafiles by type undefined?

Please consider metafiles like

#include <type_traits> template <typename T, TN, T M> struct Sum : std::integral_constant <T, N + M> {}; template <typename T, TN, T M> struct Product : std::integral_constant <T, N * M> {}; 

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

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

Both metafiles have the same static signature. That is, they associate a T with each pair T , where T fall under the same restrictions as those imposed by std::integral_constant , and are either summed or multiplied. Therefore, we can create a common metafound for the assessment.

 template <typename T, template <typename U, U, U> class F, TN, 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 exclusively in this form, it seems unnecessary to pollute Sum and Product with the std::integral_constant structure. To show you that we can do without this, consider the following:

 template <typename T, TN, TM, TR = N + M> struct Sum; template <typename T, TN, TM, TR = N * M> struct Product; template <typename> struct EvaluateBinaryOperator; template <typename T, template <typename U, U, U, U> class F, TN, TM, 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 the Sum and Product members, we specialize in the default argument and only retrieve it in the EvaluateBinaryOperator . As an added bonus, Sum and Product can be left undefined, making them trivially non-enumerable and non-constructible, and the syntax looks much cleaner. Now, here is the catch. What if we want all of our metafiles to have a single static interface? That is, if you enter

 template <typename...> struct Tuple; template <typename T, T> struct Value; 

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

 template <typename> struct Sum; template <typename T, TN, T M> struct Sum <Tuple <Value <T, N>, Value <T, M> > > : std::integral_constant <T, N + M> {}; template <typename> struct Product; template <typename T, TN, T M> struct Product <Tuple <Value <T, N>, Value <T, M> > > : std::integral_constant <T, N * M> {}; 

Now we would like to convert them into something like:

 template <typename, typename> struct Sum; template <typename T, TN, TM, 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, TN, TM, typename R = Tuple <Value <T, N * M> > > struct Product <Tuple <Value <T, N>, Value <T, M> >, R>; 

So we can extract the values โ€‹โ€‹using

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

Those of you familiar with the C ++ standard will immediately point out 14.5.5 / 8: "The list of parameters for the specialization template should not contain the default arguments for the template," followed by a tantalizing footnote: "There is no way in which they can be used" . In fact, giving almost any modern compiler, this code gives a compiler error in the specialized Sum and Product templates about violation of the standard. Besides evidence of the above footnote, deprived of the authorโ€™s imagination; we created the right use case for them.

Now you can ask the question: are there other ways to achieve a similar effect when Sum and Product remain undefined / incomplete types, thereby being trivially undetectable and unconstructible, responsible for the operation? Any suggestions are welcome. Thanks in advance.

+4
source share
1 answer

Metaprogramming is complicated. This was not a developed feature of the language; it was discovered. Thus, itโ€™s hard to do it right - therefore, people have come up with conventions on how to name metafunctions, so let other programmer guides understand the code. One of the most important conventions is that to get the result of the metafile you do this:

 typename metafunc<some_args...>::type 

Your various suggestions for writing Sum do not conform to this convention at all, and I think that even many very experienced template metaprogrammers will find it difficult to go over what you do and even rely on rule changes for how reference partial specialization works. However, this is not a convincing example for changing these rules. Let me suggest something better instead.

Types, in metaprogramming, are first-class citizens. Everything just works with types. There are no values โ€‹โ€‹or template templates. At best, they are clumsy. The fact that you need to write Sum<int, 1, 2> sucks. In addition, in principle, it is impossible to write general metafiles when you need to consider the values โ€‹โ€‹or patterns of templates with a different number of arguments. So try to keep everything type.

One of the concepts that Boost.MPL uses is the metafunction class . We can do a bit of C ++ 11-ify and say that the metafunction class is some class that looks like this:

 struct C { template <typename... Args> // doesn't have to be variadic using apply = /* whatever */; }; 

Using this idea, we can say that the EvaluateBinaryOperator will look like this:

 template <typename Op, typename A1, typename A2> struct EvaluateBinaryOperator { using type = typename Op::template apply<A1, A2>; }; 

Notice how clean it is when everything is type! Of course, the syntax for calling apply less than that of steller, but it is very simple. In fact, we can generalize to:

 template <typename Op, typename... Args> struct EvaluateOperator { using type = typename Op::template apply<Args...>; }; 

Easily. Now back to Sum . Types are first class people, so they will no longer take values. He will accept types. But we can still guarantee that types are integral constants with the same type:

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

This is consistent with the metafunction class model, so we can use it with an EvaluateOperator , for example:

 std::cout << EvaluateOperator<Sum, std::integral_constant<int, 1>, std::integral_constant<int, 2> >::type::value << std::endl; // prints 3 

Creating an impl with two types that are not integral constants of the same base type will give you the incomplete type error you wanted.

Using metafunction classes also gives you the advantage that you can curry. You cannot โ€œreturnโ€ the class template from the metafile, but you can return the metafunction class:

 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 
+4
source

All Articles