LaVOZs

The World’s Largest Online Community for Developers

'; c++ - Ensuring type safety without variants - LavOzs.Com

Consider the following classes:

template <typename T>
class A {
 public:
  A(B<T> b) : b_(b) { }

  T foo() {
    return b_.foo();
  }
 private:
  class B<T> b_;
}

template typename<T>
class B {
 public:
  T foo();
}

This nicely enforces typing across the stack (you can keep adding more layers and type them on a single type. However, I would like to have two different options on layer two:

template <typename T, typename Y>
class A {
 public:
  T foo() {
    return b_.foo();
  }
  Y foo() {
    return c_.foo();
  }

 private:
  class B<T> b;
  class C<Y> c;

}

template typename<T>
class B {
 public:
  T foo();
}

template typename<T>
class C {
 public:
  T foo();
}

Is there some way I could templetize the class with multiple typenames and achieve these scheme? Mind, in some situations T and Y might be the same, so there must be some more differentiations (for example B<T> vs C<Y>).

I believe I could have multiple functions foo1 and foo2 returning different values. However, I am looking for extensible solution where the customers can provide their own typenames and potentially more than two. Ideally, there would be a single overload (maybe taking identity of the internal class?)

Your idea is hard to understand, but I think you mean to take a list of template<typename> typenames and an equal number of typenames and construct the product of their zipped application?

// wrap a template<typename> typename into a normal type
// e.g. index_of_v<std::vector, std::vector> fails, but
// index_of_v<suspend<std::vector>, suspend<std::vector>> gives 0
template<template<typename> typename>
struct suspend { };
// search for X in Xs and produce a static constexpr std::size_t member values containing the index
// This is just the natural functional programming way to search a linked list
// indexOf(x, Nil) = undefined
// indexOf(x, Cons(y, xs)) = x == y ? 0 : 1 + indexOf(x, xs)
// deriving from std::integral_constant is really just noise;
// could be replaced with an explicit member definition
// but it's considered idiomatic to do this for some reason that I've forgotten
template<typename X, typename... Xs>
struct index_of { }; // base case, the default template only fires when Xs is empty, so the index is undefined 
template<typename X, typename... Xs>
struct index_of<X, X, Xs...> : std::integral_constant<std::size_t, 0> { }; // if X is at the top of the list, it has index 0
template<typename X, typename Y, typename... Xs>
struct index_of<X, Y, Xs...> : std::integral_constant<std::size_t, index_of<X, Xs...>::value + 1> { }; // if it isn't, find it's index relative to the tail of the list, then shift it to be the index relative to the whole
// instead of writing index_of<X, Xs..>::value, write index_of_v<X, Xs...>
// this is a convention that you see in the standard library
template<typename X, typename... Xs>
inline constexpr std::size_t index_of_v = index_of<X, Xs...>::value;

// a class cannot have two lists of variadic template parameters
// the easiest thing to do is split the templating into two stages
// the outer template, choices, takes a list of templates as parameters
// template<typename T> class std::vector;
// int is a "base" type, so you can pass int to std::vector<int>
// but std::vector is not like int: std::vector<std::vector> is meaningless
// std::vector alone is just a template<typename> typename
// a function from types to types
template<template<typename> typename... Cs>
struct choices {
    // 2nd "stage" takes the list of "normal" types
    template<typename... Ts>
    class type {
        // apply each template to the corresponding base type
        // the resulting list of base types is passed to std::tuple
        // there's a "field" for each Cs<Ts> inside the tuple
        std::tuple<Cs<Ts>...> parts;

        public:
        // hopefully self-explanatory
        type(Cs<Ts>... parts) : parts(parts...) { }

        // return the result of calling foo() on the container identified by C
        template<template<typename> typename C>
        auto foo() {
            // we know the list of all the Cs,
            // so just find the index of C in it and pass that to std::get
            // then pass in parts to get the desired object, then call foo()
            return std::get<index_of_v<suspend<C>, suspend<Cs>...>>(parts).foo();
        }
    };

    // there's no luck for deducing Cs, since in order to *get* to types we need to specify Cs
    // (choices::type is not a thing, you always write choices<Cs...>::type)
    // But you can deduce `Ts`, by looking at the things contained in each container
    // so, deduction guide
    template<typename... Ts>
    type(Cs<Ts>...) -> type<Ts...>;
};

There's nothing particularly interesting going on here. You have a list of container templates, and a list of contained types, and each object contains a tuple of objects of the desired types. Something has to be passed to foo so it knows which object to retrieve; the sanest option is the type of the container. You could also use the contained type, but apparently those aren't unique, so that wouldn't really work. You could also just pass the index directly. Since we don't know the whole type of the object, there's an auxiliary index_of type function to find the necessary index to pass to std::get. For simplicity, index_of only takes typename arguments. Since we want to search for a template<typename> typename in a list of such, all of them are wrapped in suspend to make it work.

You recover your two A types like this:

template<typename T>
struct B {
    T x;
    B(T x) : x(x) { }
    T foo() { return x; }
};
using A1 = choices<B>;
void demonstration1() {
    A1::type a(B(5)); // note the deduction guide at work
    std::cout << "B: " << a.foo<B>() << "\n";
}

template<typename T>
struct C {
    T x;
    C(T x) : x(x) { }
    T foo() { return -x; } // keeping it interesting
};
using A2 = choices<B, C>;
void demonstration2() {
    A2::type a(B('a'), C(5));
    std::cout << "B: " << a.foo<B>() << "; C: " << a.foo<C>() << "\n";
}

Godbolt

Related
What are POD types in C++?
Why is the C++ STL is so heavily based on templates? (and not on *interfaces*)
Calling template function without <>; type inference
Creating an interface for an abstract class template in C++
Weakly defining function at link-time, and testing for override
C++ overload function by return type
What does it mean to “ODR-use” something?
Array of non-type template?
The std::transform-like function that returns transformed container
Return different types from overloaded operator[] (templates?)