"decay" a templated alias - c++

This is a followup to this question.
I'm having a templated type with a template template argument
template <template <typename...> class CONTAINER, typename NUMBERTYPE>
struct spam {
template <class T>
using Temp = CONTAINER<T>;
};
I want to write a (templated) function that takes a spam instance and returns a spam instance of a slightly different type. I want to maintain the CONTAINER template parameter from the input and only specify the NUMBERTYPE. (godbolt link)
#include <type_traits>
#include <vector>
template <template <typename...> class CONTAINER, typename NUMBERTYPE>
struct spam {
template <class T>
using Temp = CONTAINER<T>;
};
template <typename T>
auto function(T in) {
spam<T::template Temp, double> retval;
return retval;
}
int main() {
spam<std::vector, float> one;
// spam<std::vector, double> two = function(one);
auto two = function(one);
return 0;
}
This mostly works, but I wanted to check if function() returns the type I expected by receiving the expected spam<std::vector, double> rather than accepting an auto return. The non-auto version does not compile because
<source>:18:45: error: conversion from 'spam<spam<std::vector, float>::Temp,[...]>' to non-scalar type 'spam<std::vector,[...]>' requested
spam<std::vector, double> two = function(one);
~~~~~~~~^~~~~
i.e. I have a mismatch between spam<std::vector, double> and spam<spam<std::vector, float>::template Temp, doulbe>, although I expect that spam<std::vector, float>::template Temp is the same as std::vector. (In fact I can check that std::is_same_v<spam<std::vector, float>::template Temp<int>, std::vector<int>> is indeed true - i.e. after providing the template arguments to Temp I have expected/desired behaviour, just that the code I'm contributing to works mostly with the unresolved CONTAINER).
QUESTION: is there a way to normalize T::template Temp to whatever it is and remove the spam<...>::Temp from the type?

QUESTION: is there a way to normalize T::template Temp to whatever it is and remove the spam<...>::Temp from the type?
No, as far I know.
But isn't necessary, in this case, because you can rewrite function() to intercept the template-template parameter CONTAINER.
I mean: you can rewrite function() as follows
template <template <typename...> class C, typename N>
spam<C, double> function (spam<C, N> const &)
{ return {}; }
The following is a full compiling example
#include <type_traits>
#include <vector>
template <template <typename...> class CONTAINER, typename NUMBERTYPE>
struct spam
{
template <typename T>
using Temp = CONTAINER<T>;
};
template <template <typename...> class C, typename N>
spam<C, double> function (spam<C, N> const &)
{ return {}; }
int main() {
spam<std::vector, float> one;
spam<std::vector, double> two = function(one);
auto three = function(one);
}

Related

Explicitly specify template template types

I'm learning templates. If I mix up the concepts template / template-type / template-argument, please correct me.
I'm trying to write a template function that creates an object and returns it. The type of the object comes from the template argument that has to be explicitly specified.
result = createObject<ObjectType>();
This object though is supposed to be a template. A container for example. And the function is supposed to know the type of the object and its template arguments. Ex:
result = createObject<Container<ElementType>>();
I've tried to solve it with template template parameter:
template <template<class> class ContainerType, class ElementType>
auto createObject()
{
ContainerType<ElementType> result;
//do stuff...
return result;
}
//...
template<typename T>
struct Vector{};
//...
//const auto random_vec = createObject<Vector<float>>(); // ERROR.
const auto random_vec = createObject<Vector, float>();
The second case works, the first doesn't. It says candidate template ignored: invalid explicitly-specified argument for template parameter 'ContainerType'.
Is it possible to make it work like the first case? Give it something like Vector<float> and it can deduce the ContainerType to Vector and ElementType to float? Is it possible to overload or specialize this function so that it handles certain types of containers differently? Should I use concepts?
The usual way to do decomposition like this is via partial specialization, which requires a helper class template:
namespace detail {
template<class> struct create; // undefined
template<template<class T> class C,class T>
struct create<C<T>> {
static C<T> make() {/* … */}
};
}
template<class T>
T createObject() {return detail::create<T>::make();}
The primary template can be defined if you want to support the general case, and other specializations may be added for other kinds of templates like std::array.
You could create a type trait to check if the type is instantiated from a template:
#include <type_traits>
// trait to check if the type is instantiated from a template
template<typename T>
struct is_template_instance_type : std::false_type {};
template<template<class,class...> class C, class T, class... Rest>
struct is_template_instance_type<C<T,Rest...>> : std::true_type {
using class_type = C<T,Rest...>;
using value_type = T;
// using rest_types = std::tuple<Rest...>;
};
// Helper variable template - if needed for something later
template<class T>
inline constexpr bool is_template_instance_type_v = is_template_instance_type<T>::value;
You could then add overloads:
template<class T, class C = is_template_instance_type<T>, class U = typename C::class_type>
auto createObject() {
U result;
// typename C::value_type x; // if you need the value type
return result;
}
template<template<class,class...> class C, class T, class... Rest>
auto createObject() {
return createObject< C<T,Rest...> >();
}
And it would then work with Vector<float>, Vector, float but not float for example.
Demo
You can simply go like this:
template<typename T, typename V = typename T::value_type>
T createObject()
{
T t {}; // T will be e.g std::vector<int>
V v {}; // V will be int
// do work...
t.push_back(v++);
t.push_back(v++);
// ...work done
return t;
}
Than you can use it like this:
int main ()
{
auto obj1 = createObject<std::vector<int>>();
auto obj2 = createObject<std::list<double>>();
return 0;
}

Convert std::variant to std::tuple of template class instances

transform_v2t function in the code below builds a tuple of template class A instances:
template <typename T>
struct A
{
T val;
};
template <class V, template <class> class T, std::size_t... index>
inline constexpr auto transform_v2t(std::index_sequence<index...>)
{
return std::make_tuple(T<std::variant_alternative_t<index, V>>() ...);
}
template <class V, template <class> class T>
inline constexpr auto transform_v2t()
{
return transform_v2t<V, T>(std::make_index_sequence<std::variant_size_v<V>>());
}
typedef std::variant<bool, char, int, float, double, std::string> V;
int main()
{
auto t1 = transform_v2t<V, A>();
}
is it possible to apply the same transform_v2t function to a class with two template arguments, for example:
template <typename P, typename T>
struct B
{
P other_val;
T val;
};
with P specialized as int?
with a pseudo code it can be something like this:
template <class T> typedef B<int, T> PartiallySpecializedB;
auto t2 = transform_v2t<V, PartiallySpecializedB>();
see online sample code.
Never use typedef in post-C++11 code, always prefer using (known as alias declarations).
Not only are they easier to read because the name you're declaring is on the left (as opposed to... wherever):
using V = std::variant<bool, char, int, float, double, std::string>;
... but they also have support for alias template declarations:
template <class T>
using PartiallySpecializedB = B<int, T>;
auto t2 = transform_v2t<V, PartiallySpecializedB>();

Why substitution fails in this function template?

I have a set of template functions which receive an index (is an int in the example) and return a value of the given type, I've used SFINAE to separate std::string from arithmetic types:
// 1
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
t(int) { ... }
// 2
template <typename T>
typename std::enable_if<std::is_same<std::string, T>::value, T>::type
t(int) { ... }
// 3
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { ... }
Also, there's a function which receives a container and fills it up using the functions above:
template <typename C>
C c(int)
{
C r{};
std::insert_iterator<C> iterator(r, r.begin());
*iterator = t<typename C::value_type>(0);
return r;
}
The goal of t is to tell apart numbers and strings, but if a pair is provided (because it comes from an associative container) then, t should split each pair component in two different t calls with the first and second types.
While deserializing non-associative containers it works but using associative containers compilation fails:
using vi = std::vector<int>;
using mii = std::map<int, int>;
auto o = c<vi>(0); // Deserialize vector
auto p = c<mii>(0); // Deserialize map
The compilation fails at the point of deserializing one element of the container:
*iterator = t<typename C::value_type>(0);
For non-associative containers C::value_type is a type which mets one of the conditions of the first two versions of t, but for associative containers C::value_type is a pair and should fail for versions #1 and #2 of t but not for the #3 version of t function; the issue is that it fails for the three of them:
error: no matching function for call to 't'
*iterator = t<typename C::value_type>(0);
^~~~~~~~~~~~~~~~~~~~~~~~~
note: in instantiation of function template specialization 'c<std::map<int, int>>' requested here
auto p = c<mii>(0);
^
note: candidate template ignored: requirement 'std::is_arithmetic<pair<const int, int> >::value' was not satisfied [with T = std::pair<const int, int>]
t(int) { ... }
^
note: candidate template ignored: requirement 'std::is_same<std::string, pair<const int, int> >::value' was not satisfied [with T = std::pair<const int, int>]
t(int) { ... }
^
note: candidate template ignored: invalid explicitly-specified argument for template parameter 'T'
T<P ...> t(int) { ... }
^
Apparently the compiler is complaining about of the lack of template-template parameters but, if I get rid of SFINAE the error vanishes:
template <typename T>
T
t(int) { return {}; }
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { return {}; }
template <typename C>
C c(int)
{
C r{};
std::insert_iterator<C> iterator(r, r.begin());
*iterator = t<typename C::value_type>(0);
return r;
}
int main()
{
using vi = std::vector<int>;
using mii = std::map<int, int>;
auto o = c<vi>(0);
auto p = c<mii>(0);
// print 0
for (auto &v : o) std::cout << v << '\n';
// print 00
for (auto &v : p) std::cout << v.first << v.second << '\n';
return 0;
}
It looks like SFINAE is forcing the template-template parameter to be required instead of deduced, why is this happening? How should I solve it?
Code is available in Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ.
It looks like (from your comment and edit), that you want to execute different functions depending on the given template parameters. The easiest way to do this is to use a class, since classes are much more flexible regarding specialization. Here is a small example of what you could do:
// initial declaration (without definition), the second template
// parameter will be used to enable some specializations
template <class T, class = void>
struct deserializer;
// specialization for arithmetic types
template <class T>
struct deserializer<
T, std::enable_if_t<std::is_arithmetic<T>::value>> {
T operator()() const {
}
};
// specialization for std::string
template <>
struct deserializer<std::string> {
std::string operator()() const {
}
};
// specialization for std::pair<U, V>
template <class U, class V>
struct deserializer<std::pair<U, V>> {
std::pair<U, V> operator()() const {
}
};
Then in your function c:
deserializer<typename C::value_type> ds;
*iterator = ds();
You can also add an intermediate generic function if you don't want to create an object of type deserializer each time:
template <class T>
T deserialize() {
return deserializer<T>{}();
}
But I think your goal here is to deserialize multiple objects, so having a functor is not that bad in that case.
Why does the deduction fails in your case?
Actually, there is no deduction here since deduction works with arguments and you are using a return type. The problem here is that this instantiation of t:
t<std::pair<int, int>>
...will never match this declaration of t:
template <template <class... > class, class... >
auto t();
Because you would need:
t<std::pair, int, int>
...to match such template signature. The only template signature that could be matched using t<typename C::value_type> is a signature of the form:
template <class T, /* something */>
...where /* something */ is either a variadic template parameter (class...), or a list of defaulted template parameters (class X = void, int N = 0), or a combination of both.
The problem here is that the original t and the new t have different template parameters:
// original.
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { ... }
// new.
template <typename C>
C c(int)
Note not only does the original t have (possibly) more than 1 template parameter, but the first parameter is a template template parameter, not a type parameter.
You also seem confused regarding template argument deduction. Template argument deduction deduces the template arguments from the function arguments. All of your functions have a single int parameter, so no deduction is taking place.
In other words, t<typename C::value_type>(0) can't work with the original function, because std::pair<const int, int> is not a valid template template parameter. You would need to write t<std::pair, const int, int>(0).
If your question is how to use SFINAE to accept a "container" (not really, because containers can have non-type template parameters), then this should work:
template<typename T>
struct is_container : std::false_type { };
template<template<typename...> class C, typename... Ts>
struct is_container<C<Ts...>> : std::true_type { };
template <typename T>
typename std::enable_if<is_container<T>::value, T>::type
t(int) { ... }

Template template parameter simple example

First I was learning about template template parameters, and I started wondering if I had a vector<vector<int>>, if I could make a template that extracts out the type int from there.
But, in the process of trying to build an example, I can't even get a single-level template parameter template function to work!
#include <iostream>
#include <vector>
template<
template<class> class C2,
class I
>
void for_2d(const C2<I>& container)
{
for( auto j : container ){
std::cout << j;
}
}
int main() {
std::vector<int> cont;
for_2d(cont);
return 0;
}
This produces:
17 : <source>:17:5: error: no matching function for call to 'for_2d'
for_2d(cont);
^~~~~~
8 : <source>:8:6: note: candidate template ignored: substitution failure : template template argument has different template parameters than its corresponding template template parameter
void for_2d(const C2<I>& container)
^
1 error generated.
Compiler exited with result code 1
The thing you are missing is that vector has multiple template arguments (most of them has default value).
You need to prepare your function for this
template<
template<class...> class C2,
class I
>
void for_2d(const C2<I>& container)
{
for( auto j : container ){
std::cout << j;
}
}
Notice the dots after class
+1 for the Bartosz Przybylski's answer, that explain why your example doesn't compile, but you want
extracts out the type int from there
You use auto j : container, so you're using (at least) C++11; so I suggest you the implementation of a specific, and recursive, type traits.
I propose the following firtType
First of all, the generic (not specialized) version (that is the recursion terminal)
template <typename T>
struct firstType
{ using type = T; };
Next the specialization for std::vector and other containers similar container (that receiving a seguence of types)
template <template <typename...> class C, typename T0, typename ... Ts>
struct firstType<C<T0, Ts...>>
{ using type = typename firstType<T0>::type; };
This specialization works with a lot of containers but not with std::array, that receive a type and a number; the following is a specialization for std::array
template <template <typename, std::size_t> class C, typename T, std::size_t N>
struct firstType<C<T, N>>
{ using type = typename firstType<T>::type; };
Other specializations may be required.
The following is a full working example
#include <array>
#include <vector>
#include <type_traits>
template <typename T>
struct firstType
{ using type = T; };
template <template <typename...> class C, typename T0, typename ... Ts>
struct firstType<C<T0, Ts...>>
{ using type = typename firstType<T0>::type; };
template <template <typename, std::size_t> class C, typename T, std::size_t N>
struct firstType<C<T, N>>
{ using type = typename firstType<T>::type; };
int main ()
{
std::vector<int> vi;
std::array<long, 42U> al;
std::vector<std::vector<short>> vvs;
static_assert( std::is_same<typename firstType<decltype(vi)>::type,
int>::value, "!" );
static_assert( std::is_same<typename firstType<decltype(al)>::type,
long>::value, "!" );
static_assert( std::is_same<typename firstType<decltype(vvs)>::type,
short>::value, "!" );
}

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"
}