为什么在此函数模板中替换失败?

时间:2017-09-12 13:35:15

标签: c++ templates sfinae

我有一组接收索引的模板函数(在示例中是int)并返回给定类型的值,我使用SFINAE将std::string与算术类型:

// 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) { ... }

此外,还有一个功能,它接收一个容器并使用上述功能填充它:

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

t的目标是区分数字字符串,但如果提供了一对(因为它来自关联容器),那么, t应使用第一种和第二种类型在两个不同的t调用中拆分每对组件。

在反序列化非关联容器时,它可以工作,但使用关联容器编译失败:

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

在反序列化容器的一个元素时,编译失败了:

*iterator = t<typename C::value_type>(0);

对于非关联容器C::value_type是一种类型,其中包含t的前两个版本的条件之一,但对于关联容器C::value_type是一对且应该失败t的版本#1和#2,但t函数的#3版本没有版本;问题在于它们中的三个失败了:

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) { ... }
         ^

显然编译器抱怨缺少模板模板参数但是,如果我摆脱SFINAE,错误就会消失:

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

看起来SFINAE强制要求模板模板参数而不是推导出来,为什么会发生这种情况?我该如何解决?

代码可在 Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ

中找到

2 个答案:

答案 0 :(得分:2)

看起来(根据您的评论和编辑),您希望根据给定的模板参数执行不同的功能。最简单的方法是使用类,因为类在专业化方面更灵活。以下是您可以做的一个小例子:

// 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 {

    }
};

然后在你的函数c中:

deserializer<typename C::value_type> ds;
*iterator = ds();

如果您不想每次都创建deserializer类型的对象,也可以添加中间通用函数:

template <class T>
T deserialize() {
    return deserializer<T>{}();
}

但我认为你的目标是反序列化多个对象,因此在这种情况下使用仿函数并不是那么糟糕。

为什么扣除在您的情况下失败?

实际上,由于演绎适用于参数并且您使用的是返回类型,因此此处没有任何推论。这里的问题是t的实例化:

t<std::pair<int, int>>

...永远不会匹配t的声明:

template <template <class... > class, class... >
auto t();

因为你需要:

t<std::pair, int, int>

...匹配此类模板签名。可以使用t<typename C::value_type>匹配的唯一模板签名是以下形式的签名:

template <class T, /* something */>

...其中/* something */是可变参数模板参数(class...),或默认模板参数列表(class X = voidint N = 0)或其组合两者都有。

答案 1 :(得分:1)

此处的问题是原始t和新t具有不同的模板参数:

// original.
template <template <typename ...> class T, typename ... P>
T<P ...> t(int) { ... }

// new.
template <typename C>
C c(int)

请注意,原始t不仅具有(可能)多于1个模板参数,而且第一个参数是模板模板参数,而不是类型参数。

你也似乎对模板参数推断感到困惑。模板参数推导从函数参数中推导出模板参数。您的所有函数都有一个int参数,因此不会进行任何演绎。

换句话说,t<typename C::value_type>(0)无法使用原始函数,因为std::pair<const int, int>不是有效的模板模板参数。您需要写t<std::pair, const int, int>(0)

如果您的问题是如何使用SFINAE接受“容器”(不是真的,因为容器可以有非类型模板参数),那么这应该有效:

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) { ... }