How do I access an inner template typedef present in the members of a variadic template argument pack?
-
25-05-2021 - |
Вопрос
I have some code which seems unambiguous to me, but gcc4.7 is choking on it:
#include <iostream>
#include <tuple>
using namespace std;
// Container for mixins
template<template<typename> class... Mixins>
struct Mix : Mixins<Mix<Mixins...>>... {
typedef tuple<Mixins<Mix<Mixins...>>...> types;
};
// Outer layer extracts the type tuple from the argument
template<typename T>
struct InnerCombiner {
typedef typename InnerCombiner<typename T::types>::type type;
};
// Typedef type to be a new mix of the inner mixins of the MixedMixins
template<typename... MixedMixins>
struct InnerCombiner<tuple<MixedMixins...>> {
// This line is the problem. The compiler doesn't seem to be able to make sense
// of the reference to the inner mixin template template classes
typedef Mix<MixedMixins::InnerMixin...> type;
};
template<typename Mixed>
struct A {
template<typename MixedInner>
struct InnerMixin {
void foo() { cout << "foo() loves you!" << endl; };
};
};
template<typename Mixed>
struct B {
template<typename MixedInner>
struct InnerMixin {
void bar() { cout << "bar() loves you!" << endl; };
};
};
// I'm going to write out the type I expect ic to have. Oh god, it's so nasty:
// Mix<
// A<Mix<A,B>>::InnerMixin<Mix<A<Mix<A,B>>::InnerMixin,B<Mix<A,B>>::InnerMixin>,
// B<Mix<A,B>>::InnerMixin<Mix<A<Mix<A,B>>::InnerMixin,B<Mix<A,B>>::InnerMixin>
// >
int main() {
InnerCombiner<Mix<A,B>>::type ic;
ic.bar(); // Not working.
}
Is there something wrong with accessing InnerMixins this way? It seemed pretty reasonable when I wrote it :)
Решение
I can make it work on clang 3.0 by specifying InnerMixin a template:
typedef Mix<MixedMixins::template InnerMixin...> type;
// ^^^^^^^^
but it still fails on g++ 4.8, with
3.cpp:23:52: error: parameter packs not expanded with ‘...’: 3.cpp:23:52: note: ‘MixedMixins’
Другие советы
There's a type/value mismatch, this should at the very least be MixedMixins::template InnerMixin...
. However GCC still rejects that and I found no way of cajoling it. Unfortunately I'm having a hard time proving that such a pack expansion is in fact valid. Hopefully someone more versed in the grammar could answer that point.
In a more 'lateral' approach, have you considered ditching template template parameters altogether? Not only could that ease the pain of syntax, but you can still 'rebind' the template parameters of a template specialization:
// We accept two types, a template specialization and
// a sequence of would be template parameters.
template<typename Specialization, typename T>
struct rebind;
template<
template<typename...> class Template
, typename... Old
template<typename...> class Sequence
, typename... T
>
struct rebind<Template<Old...>, Sequence<T...>> {
using type = Template<T...>;
};
template<typename S, typename... T>
using Rebind = typename rebind<S, T...>::type;
E.g. Rebind<std::vector<int>, std::tuple<double, std::allocator<double>>
is std:vector<double>
. Combine that with a parameters_of
/ParametersOf
utility to extract the template parameters of a specialization into e.g. an std::tuple
.
As a disclaimer I haven't used those techniques myself for long but I've already appreciated how I can limit the template template parameters pain points to a few, centralized spots of my code.