部分模板专门化的意外结果

Unexpected result with partial template specialisation

本文关键字:意外 结果 专门化      更新时间:2023-10-16

我在使用我的个人格式库时遇到了一些意想不到的结果。我已经将代码简化为您可以在下面或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 );
}

请注意,为了演示而减少了代码,在实际代码中,我将使用variadic函数中的迭代器来遍历输入容器,并为容器的每个元素调用executioner<ContainedType>::exec( * it );

执行此代码,我希望得到以下输出:


控制对
通用的

令我惊讶的是,没有使用std::pair的专门化,实际输出是:


控制通用的

我非常怀疑这是一个编译器错误(因为它发生在gcc 4.9clang 3.2),因此我问

我没有想到的是什么?

将变量模板代码更改为:

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);
  }
};

和你的代码将按预期工作。

原因:

我将试着用一个例子来说明为什么decltype不能像预期的那样工作:

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 );
    }
};

如果用上面的代码运行替换,您将看到条件std::is_same<decltype(it->first), const int>::valuetrue。这意味着*it的类型是std::pair<const int, std::basic_string<...>>,而不是std::pair<int, std::basic_string<...>>。因此,您的专门化"失败"。

好了,我想起来了

正如ildjarn暗示的那样,您可以使用std::remove_reference来剥离引用。我还剥离了const限定符(见此线程)。我的方便结构体是这样的:

template <typename T>
struct to_value
{
    typedef typename std::remove_const<
        typename std::remove_reference< T >::type
    >::type type;
};

你这样称呼它:

    typedef decltype( *it ) ContainedType;
    executioner< typename to_value<ContainedType>::type >::exec( *it );

现在只需要专门化值类型。

类型定义ContainedType不是std::pair<int, std::string>。它是const std::pair<const int, std::string>&。这就是为什么您的第一部分专门化不匹配的原因。你可以这样修改:

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 );
    }
};

和它匹配(coliru链接)

或者您可以使用容器的value_type,而不是decltype(*it):

typedef typename Container<Ts...>::value_type ContainedType;

后一种情况下,ContainedTypestd::pair<const int, std::string> (coliru链接)。