Variadic template pack as a key for std::unordered_map - c++

Is there a way to use a variadic template as a Key template parameter in std::unordered_map?
I tried to do it in this way:
template<typename T, typename ...Args>
class Wrapper {
public:
// some staff
private:
unordered_map<Args, T> hashmap;
};
But got this error:
Error C3520 'Args': parameter pack must be expanded in this context

Is there a way to use a variadic template as a Key template parameter in std::unordered_map?
No, as far I know: std::unordered_map require a single key type and a single value type.
But if your intention is to have a hashmap, in the Wrapper class, for every type in Args..., and if the Args... types are all different, you can obtain it through another inheritance and another level of wrapping
template <typename K, typename V>
struct wrpH
{ std::unordered_map<K, V> hashmap; };
template <typename T, typename ...Args>
class Wrapper : public wrpH<Args, T>...
{ };
The problem of this solution is that you have more hashmap in the same class and to access they you have to explicit the corresponding base struct; something as follows
w.wrpH<Args, T>::hashmap[argsValue] = tValue;
The following is a full working example
#include <iostream>
#include <unordered_map>
template <typename K, typename V>
struct wrpH
{ std::unordered_map<K, V> hashmap; };
template <typename T, typename ...Args>
class Wrapper : public wrpH<Args, T>...
{ };
int main()
{
Wrapper<int, int, long, long long> w;
w.wrpH<long, int>::hashmap[1L] = 2;
std::cout << w.wrpH<int, int>::hashmap.size() << std::endl;
std::cout << w.wrpH<long, int>::hashmap.size() << std::endl;
std::cout << w.wrpH<long long, int>::hashmap.size() << std::endl;
}
If, on the contrary, you need a single hashmap with a key that is a combination of all Args... types, you can use, as key type, a class that receive a variadic list of types as template paramenters. As suggested in comments, std::tuple is the obvious choice
std::unordered_map<std::tuple<Args...>, T> hashmap;
This works also if some types in Args... coincide.

Related

Explicit match of template template arguments

Consider this function:
template<template<class, class> class C, class T, class Alloc>
void foo(C<T, Alloc>& container) {
std::cout << container.size() << std::endl;
}
Function foo() accepts a std::vector<T, Alloc>, but it also accepts std::map<Key, T, Compare, Allocator> in C++17 because Compare and Allocator have default values. Note std::map fails in C++14.
How can I make foo() only accept templates with exactly only 2 template parameters, so it fails with std::map in C++17?
How can I make foo only accept templates with exactly only 2 template parameters, so it fails with std::map in C++17?
If you want avoid that foo() accept a container accepting three or more template parameters (so fail with std::map) is relatively simple: you can follow the rtpax's suggestion or, given a custom type traits that say if a type is based on a two type accepting container
template <typename>
struct accept2 : std::false_type
{ };
template <template <typename...> class C, typename X, typename Y>
struct accept2<C<X, Y>> : std::true_type
{ };
and a similar type traits for a three-accepting container
template <typename>
struct accept3 : std::false_type
{ };
template <template <typename...> class C, typename X, typename Y, typename Z>
struct accept3<C<X, Y, Z>> : std::true_type
{ };
you can SFINAE enable foo() only if the deduced type accept two but doesn't accept three types
template <typename C>
std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const & container)
{ }
But this solution (and also the rtpax one) has a problem: what about a container that receive before two template types and after one (or more) template not-type parameter with default values?
Or two template types and one (or more) template-template parameter with default values? Maybe with different signatures?
You can add specializations for accept3 to recognize the other cases but there are infinite combinations of type, non-type and template-template parameter, with default value, after the first two template type. So you have to write infinite specializations to intercept all cases.
This is a little unpractical.
And I suspect there isn't (in C++17) a practical solution.
Anyway, a full compiling example (avoiding three or more template types containers) follows
#include <map>
#include <vector>
#include <type_traits>
template <typename>
struct accept2 : std::false_type
{ };
template <template <typename...> class C, typename X, typename Y>
struct accept2<C<X, Y>> : std::true_type
{ };
template <typename>
struct accept3 : std::false_type
{ };
template <template <typename...> class C, typename X, typename Y, typename Z>
struct accept3<C<X, Y, Z>> : std::true_type
{ };
template <typename C>
std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const &)
{ }
int main()
{
std::vector<int> v;
std::map<int,int> m;
foo(v); // compile
//foo(m); // compilation error
}
Create a template overload of the function that takes a container with three elements. When you try to use a container with two parameters it will work, when you try and use something with three parameters with the third having a default value (like std::map) it will fail (since either one could overload it).
#include <map>
#include <vector>
#include <iostream>
template<template<class, class, class> class C, class Key, class T, class Alloc>
void foo(C<Key, T, Alloc>& container) {
std::cout << "three arguments" << std::endl;
}
template<template<class, class> class C, class T, class Alloc>
void foo(C<T, Alloc>& container) {
std::cout << "two arguments" << std::endl;
}
int main() {
std::vector<int> v;
std::map<int,int> m;
foo(v);
foo(m);
}
note that this doesn't work if someone inputs something like
template <class A, class B, class C> bar{};
since that will only match the three parameter option, so it won't be ambiguous
Add one level of indirection that uses a variadic template argument instead of two fixed ones. Then you can use good old enable_if to disable it whenever the count is not two:
template<template<class...> class C, class T, class Alloc>
void foo_impl(C<T, Alloc>& container) {
std::cout << container.size() << std::endl;
}
template<template<class...> class C, class... Args>
std::enable_if_t<sizeof...(Args) == 2> foo(C<Args...>& container) {
foo_impl(container);
}
See here how it works.
Side note: Apparently, latest clang and latest msvc do not handle it correctly yet. clang (experimental concepts) does, however.

Deduce/erase type of template template argument

When using template template arguments how can I have the template type of the template template deduced or erased?
Consider the following SSCCE:
#include <cstdint>
#include <cstddef>
#include <iostream>
using namespace std;
template<int i>
struct Value { };
template<int i>
struct BadValue { };
template<typename... G>
struct Print;
template<template<int> class ValueType, int... Is>
struct Print< ValueType<Is>... > {
static void print() {
const int is[] = { Is... };
for (int i: is)
cout << i;
cout << endl;
}
};
using V1 = Value<1>;
using V2 = Value<2>;
using V3 = Value<3>;
using BV = BadValue<1>;
int main() {
Print<V2, V1, V2, V3>::print(); // <-- fine
Print<V2, V1, V2, BV>::print(); // <-- BV used by accident
}
Deducing the template<int> class ValueType argument of the Print class to a template class like the Value and BadValue classes enforces that all the template arguments in the parameter pack to the Print class are specializations of the same ValueType template class - this is intentional. That is, the second line in the main() function causes a compile-time error as the ValueType argument cannot be deduced to match both the Value and BadValue classes. If the user by accident tries to mix the templates when using the Print template a compile time error arises, which provides a bit of diagnostic.
The above implementation, however, still has the int type fixed for the inner template argument of the ValueType template template argument. How can I erase it and have it deduced as well?
Generally speaking, when deducing a template template argument, how can I access the inner template argument?
If I understand correctly, you want that Print<V2, V1, V2, VB>::print(); generate an error that is simpler to understand.
For this, the best I can imagine is to works with static_assert()s.
In this particular case -- Print is a struct with only a partial specialization implemented and no general version implemented -- a not really but simple solution is available: implement the general version to give a static_assert() error with a message of your choice.
By example
template <typename ... G>
struct Print
{
static_assert( sizeof...(G) == 0, "not same int container for Print<>");
static void print()
{ };
};
template <template<int> class ValueType, int ... Is>
struct Print< ValueType<Is>... >
{
static void print()
{
using unused = int const [];
(void)unused { (std::cout << Is, 0)... };
std::cout << std::endl;
}
};
Unfortunately this solution accept as valid Print<>; I don't know if is good for you.
Another (better, IMHO, but more elaborate) solution can be transform the Print partial specialization in a specialization that accept variadic int containers (variadic ValueTypes instead a fixed ValueType) and, in a static_assert(), check (with a custom type traits) that all containers are the same.
Bye example, with the following custom type traits
template <template <int> class ...>
struct sameCnts : public std::false_type
{ };
template <template <int> class C0>
struct sameCnts<C0> : public std::true_type
{ };
template <template <int> class C0, template <int> class ... Cs>
struct sameCnts<C0, C0, Cs...> : public sameCnts<C0, Cs...>
{ };
you can write the Print specialization as follows
template <template <int> class ... Cs, int ... Is>
struct Print< Cs<Is>... >
{
static_assert(sameCnts<Cs...>{}, "different containers in Print<>");
static void print()
{
using unused = int const [];
(void)unused { (std::cout << Is, 0)... };
std::cout << std::endl;
}
};
If you can use C++17, you can use folding and the type traits can be written
template <template <int> class, template <int> class>
struct sameCnt : public std::false_type
{ };
template <template <int> class C>
struct sameCnt<C, C> : public std::true_type
{ };
template <template <int> class C0, template <int> class ... Cs>
struct sameCnts
: public std::bool_constant<(sameCnt<C0, Cs>::value && ...)>
{ };
and (using folding also in print() method) Print as follows
template <template <int> class ... Cs, int ... Is>
struct Print< Cs<Is>... >
{
static_assert( sameCnts<Cs...>{}, "different containers in Print<>");
static void print()
{ (std::cout << ... << Is) << std::endl; }
};
-- EDIT --
The OP ask
But how can I have the Print class accept also, for example, types that are specialized for a double non-type value instead of the int non-type values?
Not sure to understand what do you want but (remembering that a double value can't be a template non-type parameter) I suppose you want a Print that accept types with non-types template parameter when the type of this non type template parameter isn't fixed as in your example (int).
For C++11 and C++14 I think that in necessary to explicit the type of the non type values.
I mean... If you write Print as follows
template <typename ...>
struct Print;
template <typename T, template <T> class ... Cs, T ... Is>
struct Print< T, Cs<Is>... >
{
static_assert(sameCnts<Cs...>{}, "different containers in Print<>");
// ...
};
you have to use it this way
Print<int, V2, V1, V2, V3>::print();
that is explicating int (or long, or whatever) as first template parameter. This because the int type can't be deduced.
Starting from C++17 you can use auto as type for non-type template parameter, so you can write Print as follows
template <typename ...>
struct Print;
template <template <auto> class ... Cs, auto ... Is>
struct Print< Cs<Is>... >
{
static_assert( sameCnts<Cs...>{}, "different containers in Print<>");
static void print()
{ (std::cout << ... << Is) << std::endl; }
};
and the is no need to explicit the type and you can write
Print<V2, V1, V2, V3>::print();
In this case, you have to use auto instead of int also in sameCnt and sameCnts.
If you work in C++17, you can declare non-type template parameter with auto, so simply declare Is as auto..., and use auto instead of int in the function definition as possible as you can.
Of course, since type of elements of Is may be different, it may be impossible to declare the array is. Instead, you can use std::tuple and print the tuple instead.
// print_tuple is used to print a tuple
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp)>::type
print_tuple(const std::tuple<Tp...>&)
{ }
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp)>::type
print_tuple(const std::tuple<Tp...>& t)
{
std::cout << std::get<I>(t);
print_tuple<I + 1, Tp...>(t);
}
// ...
template<template<int> class ValueType, auto... Is>
// ^^^^
struct Print< ValueType<Is>... > {
static void print() {
print_tuple(std::make_tuple(Is...)); // make a tuple, and print it
}
};
LIVE EXAMPLE
The above pattern (making a tuple then dealing with the tuple) allows you to apply some complicated function to the parameter pack Is. However, if you only want to print the pack, you can alternatively use the C++17 feature fold expression instead, which is simpler.
template<template<int> class ValueType, auto... Is>
// ^^^^
struct Print< ValueType<Is>... > {
static void print() {
(std::cout << ... << Is); // fold expression, also C++17 feature
}
};
LIVE EXAMPLE

Variadic template partial specialization of a class to restrict type of template arguments

I have a class Foo that needs to have a variable number of template arguments, but these arguments need to be of a certain generic type, as opposed to being completely arbitrary. E.g
template < int I, typename T> struct Arg;
using type1 = Foo<Arg<3, double>>;
using type2 = Foo<Arg<1, int>, Arg<7, float>, Arg<1, int>>;
I am wondering what would be the best way to achieve this. I guess I need to start first with a plain variadic template
template < typename ...T >
class Foo;
From there, I could follow the recursive road
template < int I, typename T, typename ...Others>
template Foo<Arg<I, T>, Others...>
{
...
};
but the reading of this answer to another question left me wonder about my knowledge of variadic templates and how recursion can sometimes be avoided.
My question is, does the fact that the template arguments are expected to be in a relatively rigid format enable a partial specialization of Foo that would not be recursive, and that would effectively handle all Foos of the form Foo<Arg<...>,Arg<...>,...>?
This works:
#include <iostream>
template <int i, typename T> struct Arg;
template <typename ...T>
class Foo;
template <int ...Is, typename ...Ts>
class Foo<Arg<Is, Ts>...>
{
public:
static constexpr unsigned int N = sizeof...(Is);
};
int main()
{
using type2 = Foo<Arg<1, int>, Arg<7, float>, Arg<1, int>>;
std::cout << type2::N << "\n";
}
Though it might or might not be easy or convenient to use the template arguments in that form, depending on what you want to do with them.
You can do this with SFINAE. Here is a sketch:
template<class...Bs>
constexpr bool is_all_true(Bs...); // write this
template<class T>
constexpr bool is_valid_arg(); // write this
template < class=void, class...Ts >
class FooImpl;
template < class...Ts >
class FooImpl<std::enable_if_t<is_all_true( is_valid_arg<Ts>()...) >, Ts...> {
// code
};
template<class...Ts>
class Foo:FooImpl<void, Ts...> {};
now Foo is a FooImpl, which tests if your preconditions are all met.
You do have to write is_all_true and is_valid_arg, where is_valid_arg tests if T is of the form Arg<int, Type>.
As an example, in C++17, is_all_true is just return (true && ... && bs); (The true is redundant if I remember rightly, but I like it for clarity). In C++11/14 it will be harder.

Does templated using make a good pattern for unordered_map/set with custom keys [duplicate]

This question already has an answer here:
std::hash template partial specialization
(1 answer)
Closed 6 years ago.
For known reasons (elaborated e.g. here) one shouldn't customize std namespace. This cause one is discouraged to use minimalistic approach of redefining hasher for (not only) user-defined types by specialization std::hash which, as every modification of std namespace, may bring undefined behaviour. As such only valid way to use unordered collection with custom key is by creating his own hash helper struct and passing it to the collection. However passing it every time when accessing collection type is in my opinion somewhat inconvenient. On the other hand c++11 comes with a syntax of templated using to perform templated type aliasing. I haven't seen any answer on stackoverflow about unordered collections encouraging to use that approach. Are there any downsides for adopting templated using to create custom default values of templated parameters? Is there any better pattern covering general problem of introducing new/changing existing default values of templated parameters of classes from std namespace? Example applying hash of std::tuple:
#include <tuple>
#include <utility>
#include <unordered_map>
template <class T>
struct my_hash: std::hash<T> { };
template <
class Key,
class T,
class Hash = my_hash<Key>,
class KeyEqual = std::equal_to<Key>,
class Allocator = std::allocator< std::pair<const Key, T> > >
using my_unordered_map = std::unordered_map<Key, T, Hash, KeyEqual, Allocator>;
template <class...>
struct integral_sequence { };
template <int Start, int End, class = integral_sequence<>>
struct make_integral_sequence_impl;
template <int Start, int End, class... Integrals>
struct make_integral_sequence_impl<Start, End, integral_sequence<Integrals...> >: make_integral_sequence_impl<Start+1, End, integral_sequence<Integrals..., std::integral_constant<int, Start>>> { };
template <int StartAndBegin, class... Integrals>
struct make_integral_sequence_impl<StartAndBegin, StartAndBegin, integral_sequence<Integrals...>>{
using type = integral_sequence<Integrals..., std::integral_constant<int, StartAndBegin>>;
};
template <int Start, int End>
using make_integral_sequence = typename make_integral_sequence_impl<Start, End>::type;
template <class Tuple>
struct sizer;
template <class... Args>
struct sizer<std::tuple<Args...>> {
static constexpr size_t value = sizeof...(Args);
};
template <class Tuple, class Indices = make_integral_sequence<1, sizer<Tuple>::value - 1 > >
struct tuple_tail_impl;
template <class Head, class... Tail, class... Indices>
struct tuple_tail_impl<std::tuple<Head, Tail...>, integral_sequence<Indices...>> {
using type = std::tuple<Tail...>;
std::tuple<Head, Tail...> tuple;
std::tuple<Tail...> get() {
std::tuple<Tail...> result { std::get<Indices::value>(tuple)... };
return result;
}
};
template <class Tuple>
typename tuple_tail_impl<Tuple>::type tuple_tail(const Tuple &t) {
tuple_tail_impl<Tuple> tti { t };
return tti.get();
}
template <class First, class... Other>
struct my_hash<std::tuple<First, Other...>> {
std::size_t operator()(const std::tuple<First, Other...>& val) const {
return 805306457 * my_hash<std::tuple<Other...>>()(tuple_tail(val)) + my_hash<First>()(std::get<0>(val));
}
};
template <class First>
struct my_hash<std::tuple<First>> {
std::size_t operator()(const std::tuple<First>& val) const {
return my_hash<First>()(std::get<0>(val));
}
};
int main() {
my_unordered_map<std::tuple<int, int>, int> mum;
mum[std::make_tuple(1, 2)] = 10;
mum[std::make_tuple(2, 3)] = 20;
}
I see 2 drawbacks:
First drawback is disputable - I'd just use "raw" std types, for me such code is easier to read/understand:
using mkey = std::tuple<int, int>;
std::unordered_map<mkey, int, my_hash<my_key>> mum;
Second is that some newer version of C++ standard might have other defaults - so your projects will be in trouble of not being confirmed to new C++ standard, like - I can easy imagine that they change default comparator to std::equal_to<> for all types and (what is much less probable) allocator parameter type from pair to tuple - deprecating pair - or even to std::allocator<> expressing that it is not really important this parameter type to allocator...

Template specialization on template member of template class

This is probably only a syntax problem.
So i have this template class :
template <typename String, template<class> class Allocator>
class basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
And another one :
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
};
Now i want to specialize the second one's T parameter with the first one's inner typedef array_container for any given type.
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::template array_container<T>>
{
};
But this specialization doesn't seem to be matched when i pass an std::vector as the last parameter.
If i create a temporary hard coded typedef:
typedef basic_data_object<std::string, std::allocator<std::string>> data_object;
And use it for the specialization, everything works :
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
data_object::template array_container<T>>
{
};
What did i miss ? :)
Alternatively what is the best (smallest / cleanest) way to make this work ?
The C++ standard says, in [temp.class.spec.match] paragraph 2:
A partial specialization matches a given actual template
argument list if the template arguments of the partial
specialization can be deduced from the actual template
argument list (14.8.2).
14.8.2 is [temp.arg.deduct] i.e. the clause describing template argument deduction for function templates.
If you modify your code to use a similar function template and attempt to call it, you will see that the arguments cannot be deduced:
template <typename String, typename T>
void deduction_test(String,
typename basic_data_object<String, std::allocator>::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{});
}
(I removed the Allocator parameter, since there's no way to pass a template template parameter as a function argument and in the basic_data_object type it's a non-deduced context, I don't believe it affects the result.)
Both clang and GCC say they cannot deduce T here. Therefore the partial specialization will not match the same types used as template arguments.
So I haven't really answered the question yet, only clarified that the reason is in the rules of template argument deduction, and shown an equivalence with deduction in function templates.
In 14.8.2.5 [temp.deduct.type] we get a list of non-deduced contexts that prevent deduction, and the following rule in paragraph 6:
When a type name is specified in a way that includes a non-deduced context, all of the types that comprise that type name are also non-deduced.
Since basic_data_object<String, Allocator> is in a non-deduced context (it is a nested-name-specifier, i.e. appears before ::) that means the type T is also non-deduced, which is exactly what Clang and GCC tell us.
With your temporary hardcoded typedef there is no non-deduced context, and so deduction for T succeeds using the deduction_test function template:
template <typename String, typename T>
void deduction_test(String,
typename data_object::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{}); // OK
}
And so, correspondingly, your class template partial specialization can be matched when it uses that type.
I don't see a way to make it work without changing the definition of get_data_object_value, but if that's an option you can remove the need to deduce the array_container type and instead use a trait to detect whether a type is the type you want, and specialize on the result of the trait:
#include <string>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
class basic_data_object
{
public:
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
template<typename T>
struct is_ac : std::false_type { };
template<typename T>
struct is_ac<array_container<T>> : std::true_type { };
};
template <typename String, template<class> class Allocator, typename T, bool = basic_data_object<String, Allocator>::template is_ac<T>::value>
struct get_data_object_value
{
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value<String, Allocator, T, true>
{
void f() { }
};
int main()
{
get_data_object_value<std::string,std::allocator,std::vector<short>> obj;
obj.f();
}
This doesn't really scale if you wanted several class template partial specializations, as you would need to add several bool template parameters with default arguments.
For some reason, the problem seems to stem from the double level of templates. I'll leave you check the 3 test cases below, they are simple:
Remove the template arguments of First: works as expected
Make First a template, but the inner type a plain one: works as expected
Make both First and the inner type templates: compiles but the output is unexpected
Note: the template template parameter Allocator is useless to reproduce the issue, so I left it out.
Note: both GCC (ideone's version, 4.8.1 I believe) and Clang (Coliru version, 3.4) compile the code, and yet produce the same baffling result
From the 3 above examples, I deduce:
that this is NOT a non-deducible context issue; otherwise why would (2) work ?
that this is NOT an alias issue; otherwise why would (1) work ?
And therefore that either the problem is much hairier than the current hints would make us believe OR that both gcc and Clang have a bug.
EDIT: Thanks to Jonathan Wakely who patiently educated me enough that I could finally understand both the Standard wording related to this case and how it applied. I will now attempt to explain this (again) in my own words. Please refer to Jonathan's answer for the exact Standard quotes (it all sits in [temp.deduct.type])
When deducing template parameters (Pi), whether for functions or classes, the deduction is done independently for each and every argument.
Each argument need provide zero or one candidate Ci for each parameter; if an argument would provide more than one candidate, it provides none instead.
Thus, each argument produces a dictionary Dn: Pi -> Ci which maps a subset (possibly empty) of the template parameters to be deduced to their candidate.
The dictionaries Dn are merged together, parameter by parameter:
if only one dictionary has a candidate for a given parameter, then this parameter is accepted, with this candidate
if several dictionaries have the same candidate for a given parameter, then this parameter is accepted, with this candidate
if several dictionaries have different incompatible (*) candidates for a given parameter, then this parameter is rejected
If the final dictionary is complete (maps each and every parameter to an accepted candidate) then deduction succeeds, otherwise it fails
(*) there seems to be a possibility for finding a "common type" from the available candidates... it is of no consequence here though.
Now we can apply this to the previous examples:
1) A single template parameter T exists:
pattern matching std::vector<int> against typename First::template ArrayType<T> (which is std::vector<T>), we get D0: { T -> int }
merging the only dictionary yields { T -> int }, thus T is deduced to be int
2) A single template parameter String exists
pattern matching std::vector<int> against String, we get D0: { String -> std::vector<int> }
pattern matching std::vector<int> against typename First<String>::ArrayType we hit a non-deducible context (many values of String could fit), we get D1: {}
merging the two dictionaries yields { String -> std::vector<int> }, thus String is deduced to be std::vector<int>
3) Two template parameters String and T exist
pattern matching std::vector<char> against String, we get D0: { String -> std::vector<char> }
pattern matching std::vector<int> against typename First<String>::template ArrayType<T> we hit a non-deducible context, we get D1: {}
merging the two dictionaries yields { String -> std::vector<char> }, which is an incomplete dictionary (T is absent) deduction fails
I must admit I had not considered yet that the arguments were resolved independently from one another, and therefore than in this last case, when computing D1 the compiler could not take advantage of the fact that D0 had already deduced a value for String. Why it is done in this fashion, however, is probably a full question of its own.
Without the outer template, it works, as in it prints "Specialized":
#include <iostream>
#include <vector>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename T>
struct Second < typename First::template ArrayType<T> > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<int> > second;
second.go();
return 0;
}
Without the inner template, it works, as in it prints "Specialized":
#include <iostream>
#include <vector>
template <typename String>
struct First {
using ArrayType = std::vector<int>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename String>
struct Second < String, typename First<String>::ArrayType > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<int>, std::vector<int> > second;
second.go();
return 0;
}
With both, it fails, as in it prints "General":
#include <iostream>
#include <vector>
template <typename String>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename String, typename T>
struct Second < String, typename First<String>::template ArrayType<T> > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<char>, std::vector<int> > second;
second.go();
return 0;
}
The answer of Jonathan Wakely gives the reason why your code does not work.
My answer shows you how to solve the problem.
In your example, the container type over which you want to specialize is defined outside of basic_data_object thus you can of course use it directly in your specialization:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,std::vector<T,A>>
{ };
This definitely conforms with the standard and works with all compilers.
In the case where the type is defined in basic_data_object, you can move it out of the class.
Example: Instead of
template<typename S, template<class> class A>
struct a_data_object
{
template<typename T>
struct a_container
{ };
};
write this:
template<typename S, template<class> class A, typename T>
// you can perhaps drop S and A if not needed...
struct a_container
{ };
template<typename S, template<class> class A, typename T>
struct a_data_object
{
// use a_container<S,A,T>
};
Now you can specialize with:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,a_container<S,A,T>>
{ };
Note: The next "solution" is apparently a bug with GCC 4.8.1.
If the container is only defined in an enclosing template and can not be moved out you can do this:
Get the container type out of basic_data_object:
template<typename S, template<class> class A, typename T>
using bdo_container = basic_data_object<S,A>::array_container<T>;
Write a specialization for this type:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,bdo_container<S,A,T>>
{ };
Alternatively what is the best (smallest / cleanest) way to make this work?
Arguably, it is:
Write a SFINAE trait template Tr<String,Allocator,T> that determines whether T is the
same as basic_data_object<String,Allocator>::array_container<T::E>
for some type E - if such there be - that is T::value_type.
Provide template get_data_object_value with a 4th parameter
defaulting to Tr<String,Allocator,T>::value
Write partial specializations of get_data_object_value instantiating that
4th parameter as true, false respectively.
Here is a demo program:
#include <type_traits>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
struct basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
/*
A trait template that has a `static const bool` member `value` equal to
`true` if and only if parameter type `T` is a container type
with `value_type E` s.t.
`T` = `basic_data_object<String,Allocator>::array_container<T::E>`
*/
{
template<typename A>
static constexpr bool
test(std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
> *) {
return std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
>::value;
}
template<typename A>
static constexpr bool test(...) {
return false;
}
static const bool value = test<T>(nullptr);
};
template <
typename String,
template<class> class Allocator,
typename T,
bool Select =
is_basic_data_object_array_container<T,String,Allocator>::value
>
struct get_data_object_value;
template <
typename String,
template<class> class Allocator,
typename T
>
struct get_data_object_value<
String,
Allocator,
T,
false
>
{
static void demo() {
std::cout << "Is NOT a basic_data_object array_container" << std::endl;
}
};
template <
typename String,
template<class> class Allocator,
typename T>
struct get_data_object_value<
String,
Allocator,
T,
true
>
{
static void demo() {
std::cout << "Is a basic_data_object array_container" << std::endl;
}
};
#include <list>
#include <memory>
using namespace std;
int main(int argc, char **argv)
{
get_data_object_value<string,allocator,std::vector<short>>::demo();
get_data_object_value<string,allocator,std::list<short>>::demo();
get_data_object_value<string,allocator,short>::demo();
return 0;
}
Built with gcc 4.8.2, clang 3.4. Output:
Is a basic_data_object array_container
Is NOT a basic_data_object array_container
Is NOT a basic_data_object array_container
VC++ 2013 will not compile this for lack of constexpr support. To accommodate that
compiler the following less natural implementation of the trait may be used:
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
{
template<typename A>
static
auto test(
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
> *
) ->
std::integral_constant<
bool,
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
>::value
>{}
template<typename A>
static std::false_type test(...);
using type = decltype(test<T>(nullptr));
static const bool value = type::value;
};
Edit: This answer only works because of a bug in GCC 4.8.1
Your code works as expected if you drop the keyword template in your specialization:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
void foo() { std::cout << "general" << std::endl; }
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::array_container<T>>
// ^^^^^^ no template!
{
void foo() { std::cout << "special" << std::endl; }
};
Example tested with GCC 4.8.1:
int main() {
get_data_object_value<std::string,std::allocator,std::vector<int>> obj;
obj.foo(); // prints "special"
}