elemakil
elemakil

Reputation: 3811

Unexpected result with partial template specialisation

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

Answers (3)

lrineau
lrineau

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

djf
djf

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

Dimitrios Bouzas
Dimitrios Bouzas

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.

Reason:

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

Related Questions