THIRD EDIT:
Today I received email from VS support stating that it's know issue, which has been fixed in VS2017. So, I will stick to my workaround for the time being.
I'm working on event system and I wanted to use some metaprogramming to save on typing. Here's the code:
struct foo { using type = int; };
struct bar { using type = char; };
template<typename... Types> struct TypeList {};
//This and getScript are simplified, in reality the involve messier templates...
template<typename ReturnType>
struct ReqBuilder
{
using proxy = ReturnType(*)(void*, bool);
// ...
};
template<typename scriptType>
using getScript = ReqBuilder<scriptType>;
template<typename List, template<typename> typename Wrap> struct WrapTypeList_impl {};
template<typename...Ts, template<typename> typename Wrap> struct WrapTypeList_impl<TypeList<Ts...>, Wrap>
{
using type = TypeList<Wrap< Ts>...>;
};
template<typename List, template<typename> typename Wrap>
using WrapTypeList = typename WrapTypeList_impl<List, Wrap>::type;
//Use typedef
using list1 = TypeList<getScript<foo>, getScript<bar>>;
//Write it manually
using list2 = TypeList<ReqBuilder<foo>, ReqBuilder<bar>>;
//Use wrapper to wrap each element of typelist
using list3 = WrapTypeList<TypeList<foo, bar>, getScript>;
Goal is to have all three list result in the same TypeList. This code is simplified, in reality getScript does more work and it's actually useful. Same for ReqBuilder which contains more typedefs. So, list3 should be much easier to write than either list1 or list2.
There are more getScript, ReqBuilder-like classes and flattening of nested typelists.
Which allows to easily write list of all possible events even with complex types.
This code above works - all lists are equal. Problem is when specialize (and I want to) on nested type:
template<typename scriptType>
using getScript = ReqBuilder<typename scriptType::type>;
Then I get following error atWrapTypeList_impl::type=... line:
Error C2938 'getScript<unknown-type>' : Failed to specialize alias template
I really have no idea what that even means, but it probably has something to do with pack expansion, because when I add this specialization:
template<typename A,typename B, template<typename> typename Wrap>
struct WrapTypeList_impl<TypeList<A,B>, Wrap>
{
using type = TypeList<Wrap<A>,Wrap<B>>;
};
Then it works even with nested types. So, does anyone have any idea how to get this working please?
I'm using Visual studio 2015 Comunnity edition. And even more puzzling thing is that using intellisense - hovering over lists shows they resolved in same TypeList, even std::is_same<list1,list3>::value shows true on hover. But compiler for some reason disagrees.
EDIT: ADDED FULL CODE THAT CAUSES SAID ERROR
#include <algorithm>
struct foo { using type = int; };
struct bar { using type = char; };
template<typename... Types> struct TypeList {};
//This and getScript are simplified, in reality the involve messier templates...
template<typename ReturnType>
struct ReqBuilder
{
using proxy = ReturnType(*)(void*, bool);
// ...
};
template<typename scriptType>
using getScript = ReqBuilder<typename scriptType::type>;
template<typename List, template<typename> typename Wrap> struct WrapTypeList_impl {};
template<typename...Ts, template<typename> typename Wrap> struct WrapTypeList_impl<TypeList<Ts...>, Wrap>
{
using type = TypeList<Wrap<Ts>...>;
};
template<typename List, template<typename> typename Wrap>
using WrapTypeList = typename WrapTypeList_impl<List, Wrap>::type;
using list1 = TypeList<getScript<foo>, getScript<bar>>;
using list2 = TypeList<ReqBuilder<typename foo::type>, ReqBuilder<typename bar::type>>;
using list3 = WrapTypeList<TypeList<foo, bar>, getScript>;
int main()
{
constexpr bool A = std::is_same<list1, list2>::value;
constexpr bool B = std::is_same<list1, list3>::value;
constexpr bool C = std::is_same<list2, list3>::value;
static_assert(A, "list1 != list2");
static_assert(B, "list1 != list3");
static_assert(C, "list2 != list3");
}
Erorrs:
1> Main.cpp
1>Main.cpp(23): error C2938: 'getScript<unknown-type>' : Failed to specialize alias template
1> Main.cpp(31): note: see reference to class template instantiation 'WrapTypeList_impl<TypeList<foo,bar>,getScript>' being compiled
1>Main.cpp(23): error C3546: '...': there are no parameter packs available to expand
1>Main.cpp(36): error C3203: 'TypeList': unspecialized class template can't be used as a template argument for template parameter '_Ty2', expected a real type
1>Main.cpp(37): error C3203: 'TypeList': unspecialized class template can't be used as a template argument for template parameter '_Ty2', expected a real type
1>Main.cpp(40): error C2338: list1 != list3
1>Main.cpp(41): error C2338: list2 != list3
Adding said A,B specialization for Wrapper after variadic one solves it.
SECOND EDIT - FOUND WORKAROUND
So, original problem still exists, but after bit of trial and error, I tried to replace dependent type with separate struct, which seems to solve the problem:
template<typename scriptType>
struct getScript_impl
{
using type = ReqBuilder<typename scriptType::type>;
};
template<typename scriptType>
using getScript = typename getScript_impl<scriptType>::type;
/* REPLACING THIS WITH CODE ABOVE
template<typename scriptType>
using getScript = ReqBuilder<typename scriptType::type>;
*/
Full code, which works in VS2015 for me: http://ideone.com/kGT4qM
As I briefly stated in my comment, I think the issue is that alias template using dependent type as another template argument does not instantiate that template or something like that.(I'm not a template wizard, I really just poke at them, and see how they respond).
I've found few links that are related to this issue, but didn't have time to look at them yet(link1; link2; link3).
Related
I have a type list that I've been using but I want to make it cleaner.
My current implementation relies on all the types to be listed when declaring the type list.
template<typename... Types>
struct TypeList {};
using MyCustomTypeList = TypeList<Type1, Type2, Type3>; // Currently listed in one place. This is what I want to avoid.
I would like to build a type list is such a way where I could just write: ADD_TYPE(type) and it would add to MyCustomTypeList.
One of the requirements is that a function call whose signature looks like
template<typename... Types>
void MyFunction(TypeList<Types...>&&);
is still callable with MyFunction( MyCustomTypeList{} );
C++ 17 is available; boost or other libraries aren't.
If you want to update your list like so:
using list1 = pack<int>;
using list2 = push_back_t<list1, char>; // pack<int, char>
using list3 = push_back_t<list2, double>; // pack<int, char, double>
We can write push_back like this:
template <class T>
struct tag
{
using type = T;
};
template <class T>
using detag = typename T::type;
////////////////////////////////
template <class, class>
struct push_back {};
template <template <class...> class C, class ... Ts, class NewT>
struct push_back <C<Ts...>, NewT> : tag<C<Ts..., NewT>> {};
template <class List, class NewT>
using push_back_t = detag<push_back<List, NewT>>;
Demo
Note: I renamed TypeList to pack, as lowercase is standard for type traits.
A class WithTTMember has a template member type named TT.
struct WithTTMember {
template<typename> using TT = void;
};
Another class ExpectTT takes a template template parameter:
template< template<typename> typename TT >
struct ExpectTT {};
ExpectTT<WithTTMember::TT> can be successfully instantiated.
A third class ExpectTWithTT expects a template parameter with a template member type named TT, and instantiates ExpectTT using it:
template<typename T>
struct ExpectTWithTT {
using X = ExpectTT<typename T::TT>; // this doesn't compile
};
I expect ExpectTWithTT<WithTTMember>::X to be the same type as ExpectTT<WithTTMember::TT>. However the code above is fails to compile.
I tried injecting the faulty line with a combination of template and typename keywords following compiler messages and my instinct, but I couldn't get it to work.
How can I express what I want?
Any C++ version is fine.
You should use template keyword to tell that T::TT is a template.
template<typename T>
struct ExpectTWithTT {
using X = ExpectTT<T::template TT>;
// ^^^^^^^^
};
So, my motivation here is to determine whether the same named type declaration within several classes are the same type. In this example, I'm looking to see that all of Foo, Bar, and Baz have an internal type Q.
#include <type_traits>
template <typename N,typename ...Ns>
using equal_type_t = typename std::enable_if_t<(std::is_same_v<N, Ns> && ...), N>;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;
int main()
{
struct Qness{};
struct Foo{using Q = Qness;};
struct Bar{using Q = Qness;};
struct Baz{using Q = Qness;};
using F = EqualQ_t<Foo,Bar,Baz>;
static_assert(std::is_same_v<F,Qness>);
return 0;
}
Tested in clang9 (praise be to godbolt).
The error reported is:
#1 with x86-64 clang 9.0.0
<source>:10:31: error: pack expansion used as argument for non-pack parameter of alias template
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;
I could probably solve this by way of doing some template recursion, but I'm trying to learn to use parameter pack expansion wherever possible.
Is this possible? Is this not an allowed context? If I separate out a few individual N types, it works fine:
template <typename N1,typename N2, typename N3, typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<N1>,ExtractQ_t<N2>,ExtractQ_t<N3>>;
I have to be having a pre-coffee brain-fog and can't see where I might be hosing the syntax.
Is there an expansion variant of this that will work?
The error diagnostic tries to say that the first parameter of equal_type_t cannot be a pack, yet you are expanding a pack into it. Thus, the simple fix is to do the same thing you did earlier:
template <typename N, typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<N>, ExtractQ_t<Ns>...>;
https://godbolt.org/z/j6_HGU
The unpacking into a non-pack + pack would require template argument deduction, but that doesn't happen for alias templates, see cppreference. You would need a struct template specialization (or template function call) to get deduction.
Using SFINAE seems a little weird in this case though. If the condition is not fulfilled, you get some compiler gibberish about SFINAE thrown in your face. There are other ways to cause a hard error during compilation.
I would say the following is the idiomatic way to write the same code, which gives you a good error when there is a problem and would (not exactly coincidentally) avoid your original problem:
template <typename ...Ns>
struct equal_type;
template <typename N,typename ...Ns>
struct equal_type<N, Ns...>
{
static_assert((std::is_same_v<N, Ns> && ...), "These types must be the same!");
using type = N;
};
template <typename ...Ns>
using equal_type_t = typename equal_type<Ns...>::type;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename ...Ns>
using EqualQ_t = equal_type_t<ExtractQ_t<Ns>...>;
https://godbolt.org/z/u52mUE
For completeness, the pre-C++17 way (before fold expressions existed) does indeed use recursion:
template <typename N1, typename N2, typename ...Ns>
struct equal_type
{
static_assert(std::is_same_v<N1, N2>, "These types must be the same!");
using type = typename equal_type<N1, Ns...>::type;
};
template <typename N1, typename N2>
struct equal_type<N1, N2>
{
static_assert(std::is_same_v<N1, N2>, "These types must be the same!");
using type = N1;
};
https://godbolt.org/z/NKmMZD
Figured this was best suited to a self-answer, but this is mostly directed at Max, who asked a return question, and this response 'is too long to fit in the margins'.
If I'd never tried parameter packs, I probably wouldn't have gotten to this variation on template recursion to solve my problem, but it's probably where I might have gone to if I hadn't been educated on the real issue.
#include <type_traits>
template <typename N,typename ...Ns>
using equal_type_t = typename std::enable_if_t<(std::is_same_v<N, Ns> && ...), N>;
template <typename N>
using ExtractQ_t = typename N::Q;
template <typename N,typename ...Ns>
class EqualQ
{
public:
using type = equal_type_t<ExtractQ_t<N>,typename EqualQ<Ns...>::type>;
};
template <typename N>
class EqualQ<N>
{
public:
using type = ExtractQ_t<N>;
};
template <typename ...Ns>
using EqualQ_t = typename EqualQ<Ns...>::type;
int main()
{
struct Qness{};
struct Foo{using Q = Qness;};
struct Bar{using Q = Qness;};
struct Baz{using Q = Qness;};
using F = EqualQ_t<Foo,Bar,Baz>;
static_assert(std::is_same_v<F,Qness>);
return 0;
}
Yes, I realize it's not idiomatic, definitely less clean than either of Max's solutions, and doesn't answer my original question, but explores what I was trying to avoid.
One of the things I did discover doing it in this manner though was that aliases can't be specialized like class templates can. So point of education there too. I had to turn EqualQ into a templated struct.
The thing is, doing it this way wouldn't have educated me on why I couldn't unpack my parameter packs the way I originally wanted to, and certainly not to Max's idiomatic pattern, which I shall now be adopting. :)
Say I have a template class like so:
template < typename TParam >
class Test
{
// content
};
I want to pull out the first template parameter of TParam if it's a specialization of a class template. Something like:
template < typename TParam >
class Test
{
using TParamInner = TemplateType<TParam>::Type;
// use TParamInner here
};
Additional info:
I have access to all of C++98.
I have access to a subset of C++11.
I would prefer to avoid the stdlib if possible (assume this is
because I'm using an embedded system for which no stdlib is available and/or because I am heavily memory-constrained)
You can get close with something like:
template <class >
struct first_template_param;
template <template <class...> class Z, class T, class... Ts>
struct first_template_param<Z<T, Ts...>> {
using type = T;
}
It won't handle std::array or any other class templates that take non-type template parameters. But it'll handle all the "normal" class templates. You can always then add extra specializations for all the ones you want:
template <class T, size_t N>
struct first_template_param<std::array<T,N>> {
using type = T;
}
Thanks to #Barry for spurring the solution along.
It's not a complete answer for all template types, but it works for templates where all parameters are types, which is a large number of the most useful templates.
template < typename Head, typename ... Tail >
struct split { using first = Head; };
template <class >
struct cls_template_info; // fails on non-templates
template <template <class...> class Z, class... Ts>
struct cls_template_info<Z<Ts...>>
{
using type = typename split<Ts...>::first; // typename used to disambiguate
};
This can then be used as using T = cls_template_info<std::vector<int>>::first;.
You can't. A template type is never carried up to runtime. You have to instantiate it (this leads to a complete new type), and the compiler then generates the needed code to make it appear as if you have defined specifically for the type parameters you specified. Indeed, in old compilers (this has been solved a lot of time ago) when you instantiate a generic type in several compilation units, that lead to several repetitions of the same code in the final program. But as I've said, this has been solved time ago.
I found this construct in C-code:
template<typename T, class = decltype(std::declval<T>() < std::declval<T>())>
struct check : std::true_type {};
Now I understand what it does but I don't understand how it works. It throws a compile error if type T doesn't support the <-operator. But, apparently, when changing class to something else, the whole thing won't compile and throws a Syntax Error.
What does class = sometypename mean?
class is the same as typename here. You could also do this:
template<typename T, typename = decltype(std::declval<T>() < std::declval<T>())>
struct check : std::true_type {};
You can specify default values for template arguments. For example
template<typename X = int> struct test { };
You can also leave off the name of the template arguments if you don't use them:
template<typename = int> struct test { };
So in your example, the second template parameter is just an unnamed parameter with a default argument.
The concept that makes this work is known as SFINAE (substitution failure is not an error) and is used to implement std::enable_if<> etc. http://en.cppreference.com/w/cpp/language/sfinae