为什么替换在此功能模板中失败

Why substitution fails in this function template?

本文关键字:失败 功能 替换 为什么      更新时间:2023-10-16

i具有一组模板函数,该功能接收索引(示例中是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的目标是分开 numbers strings ,但是如果提供了一对(因为它来自关联容器),则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,但应失败#3 t功能的版本;问题是其中三个失败了:

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三三(へ՞ਊ)中找到。

它看起来像(从您的注释和编辑中),您希望根据给定的模板参数执行不同的函数。最简单的方法是使用课程,因为课程在专业方面更加灵活。这是您可以做什么的一个小例子:

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

,但我认为您的目标是对多个对象进行估算,因此在这种情况下拥有函子并不糟糕。


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

实际上,这里没有扣除,因为推论与 gragments 一起使用,并且您使用的是返回类型。这里的问题是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 */是variadic模板参数(class...),或者是默认模板参数(class X = voidint N = 0)或两者的组合。

这里的问题是原始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) { ... }