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

Why substitution fails in this function template?

我有一组模板函数接收索引(在示例中是 int)和 return 给定类型的值,我使用 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 的目标是区分 numbersstrings,但是如果提供了一对(因为它来自关联容器)然后,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 三へ( へ՞ਊ ՞)へ ハッハッ.

中可用

这里的问题是原来的t和新的t有不同的模板参数:

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

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

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

您似乎也对模板参数推导感到困惑。模板参数推导从函数参数中推导出模板参数。您的所有函数都有一个 int 参数,因此不会发生推导。

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

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

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

(从您的评论和编辑来看)您似乎想要根据给定的模板参数执行不同的功能。最简单的方法是使用 class,因为 classes 在专业化方面更加灵活。这是您可以执行的操作的一个小示例:

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

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


为什么你的推导失败了?

实际上,这里没有演绎,因为演绎适用于 arguments 并且您使用的是 return 类型。这里的问题是 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),或两者的结合。