Composing std::integral_constant values

468 views Asked by At

Consider this code

#include <iostream>
#include <type_traits>

enum Thing {Thing0, Thing1, Thing2, NumThings};
enum Object {Object0, Object1, Object2, NumObjects};

template <Thing> struct ThingValue;

template <> struct ThingValue<Thing0> : std::integral_constant<int, 5> {};
template <> struct ThingValue<Thing1> : std::integral_constant<int, 2> {};
template <> struct ThingValue<Thing2> : std::integral_constant<int, 12> {};

template <Object> struct ObjectValue;

template <> struct ObjectValue<Object0> : std::integral_constant<Thing, Thing2> {};
template <> struct ObjectValue<Object1> : std::integral_constant<Thing, Thing0> {};
template <> struct ObjectValue<Object2> : std::integral_constant<Thing, Thing1> {};

int main() {
    std::cout << ThingValue<ObjectValue<Object0>::value>::value << '\n';  // 12
}

I'm trying to define, ComposeValues<T, Value, Pack...> so that the above in main() can be written as ComposeValues<Object, Object0, ThingValue, ObjectValue>::value. So then this can be extended to any number of such compositions. Not an absolutely vital thing to do, but I'd figure that it would be a nice little exercise to define such a thing. But I'm having difficulty with the syntax:

template <typename T, T Value, template <typename> class...> struct ComposeValues;

template <typename T, T Value, template <typename> class First, template <typename> class... Rest>
struct ComposeValues<T, Value, First, Rest...> {
    static auto value = First<typename ComposeValues<T, Value, Rest...>::value>::value;
};

template <typename T, T Value, template <T> class Last>
struct ComposeValues<T, Value, Last> : std::integral_constant<T, Last<Value>::value> {};  // Won't compile.

Is this even possible what I'm trying to do?

1

There are 1 answers

3
TartanLlama On BEST ANSWER

The issue that you are having is that you can't mix template templates which take different non-type arguments. In your example, this means that ObjectValue and ThingValue cannot bind to template <typename> class....

A way to fix this is to encode your enums in some type kind of template which can hold both of them indiscriminately. A possible way to do this is just treat the enums as ints and let the user worry about passing in reasonable types.

First we create wrappers around your current types:

template <typename> struct ThingValueWrapper;
template <int I>
struct ThingValueWrapper<std::integral_constant<int,I>> 
    : ThingValue<static_cast<Thing>(I)>
{};

template <typename> struct ObjectValueWrapper;
template <int I>
struct ObjectValueWrapper<std::integral_constant<int, I>> 
    : ObjectValue<static_cast<Object>(I)>
{};

Then we can do something pretty similar to what you originally had:

template <typename T, T Value, template <typename> class...> struct ComposeValues;

template <typename T, T Value, 
          template <typename> class First, 
          template <typename> class... Rest>
struct ComposeValues<T, Value, First, Rest...>
    : std::integral_constant<int,
                             First<typename ComposeValues<T, Value, Rest...>::type>::value>
{};

template <typename T, T Value>
struct ComposeValues<T, Value> : std::integral_constant<int, static_cast<int>(Value)> {};

The only difference from your original use-case is that we need to use our wrappers instead of the original enum traits:

ComposeValues<Object, Object0, ThingValueWrapper, ObjectValueWrapper>::value