Reputation: 3811
I have encountered some unexpected results while playing around with my personal formatting library. I have reduced the code to the listing you can find below or on coliru.
#include <iostream>
#include <map>
#include <utility>
#include <string>
template <typename T>
struct executioner {
inline static void exec( const T & ){
std::cout << "generic" << std::endl;
}
};
template <typename T1, typename T2>
struct executioner<std::pair<T1, T2> > {
inline static void exec( const std::pair<T1, T2> & t ){
std::cout << "pair" << std::endl;
executioner<T1>::exec( t.first );
executioner<T2>::exec( t.second );
}
};
template <template <typename ...> class Container,
typename ... Ts>
struct executioner<Container<Ts ...> > {
inline static void exec( const Container<Ts ...> & c ){
std::cout << "cont" << std::endl;
auto it = c.begin();
typedef decltype( * it ) ContainedType;
executioner<ContainedType>::exec( * it );
}
};
template <typename T> void execute( const T & t ){
executioner<T>::exec( t );
}
int main(){
std::map<int,std::string> aMap = { { 0, "zero" }, { 1, "one" }, { 2, "two" } };
execute( aMap );
}
Note that the code is reduced for demonstration, in the actual piece, I'd use the iterator in the variadic function to loop over the input container and call executioner<ContainedType>::exec( * it );
for each of the container's elements.
Executing this code, I would expect the following output:
cont
pair
generic
To my surprise, the specialisation for std::pair
was not used, the actual output is:
cont
generic
I very much doubt that this is a compiler bug (since it happens with both gcc 4.9
and clang 3.2
) and therefore I ask
What's the catch I haven't thought about?
Upvotes: 2
Views: 188
Reputation: 6274
The typedef ContainedType
is not std::pair<int, std::string>
. It is const std::pair<const int, std::string>&
. That is why your first partial specialization does not match. You can modify it that way:
template <typename T1, typename T2>
struct executioner<const std::pair<T1, T2>&> {
inline static void exec( const std::pair<T1, T2> & t ) {
std::cout << "pair" << std::endl;
executioner<T1>::exec( t.first );
executioner<T2>::exec( t.second );
}
};
and it matches (coliru link).
Or you can use the value_type
of the container, instead of decltype(*it)
:
typedef typename Container<Ts...>::value_type ContainedType;
in the later case, ContainedType
is std::pair<const int, std::string>
as expected (coliru link).
Upvotes: 0
Reputation: 6757
Ok, it's all coming back to me now :)
As ildjarn hinted you can use std::remove_reference
to strip the reference. I also stripped the const
qualifier (see this thread). My convenience struct looks like this:
template <typename T>
struct to_value
{
typedef typename std::remove_const<
typename std::remove_reference< T >::type
>::type type;
};
You call it like this:
typedef decltype( *it ) ContainedType;
executioner< typename to_value<ContainedType>::type >::exec( *it );
Now you only need to specialize on the value type. Here's the whole thing.
Upvotes: 2
Reputation: 42889
Change your variadic template code to the following:
template <template <typename ...> class Container,
typename ... Ts>
struct executioner<Container<Ts ...> > {
inline static void exec(const Container<Ts ...> & c){
std::cout << "cont" << std::endl;
auto it = c.begin();
executioner<typename Container<Ts...>::value_type>::exec(*it);
}
};
and your code will work as expected.
I'll try to demonstrate why decltype
doesn't work as expected with an example:
template <template <typename ...> class Container,
typename ... Ts>
struct executioner<Container<Ts ...> > {
inline static void exec( const Container<Ts ...> & c ){
std::cout << "cont" << std::endl;
auto it = c.begin();
typedef decltype( (*it) ) ContainedType;
if(std::is_same<decltype(it->first), const int>::value) {
std::cout << "IS CONST !!!" << std::endl;
}
executioner<ContainedType>::exec( * it );
}
};
If you run substituting with the code above you'll see that conditional std::is_same<decltype(it->first), const int>::value
is true
. This means that the *it
is of type std::pair<const int, std::basic_string<...>>
and not std::pair<int, std::basic_string<...>>
. Thus, your specialization "fails".
Upvotes: 4