如何调用带模板的类型

3
我正在尝试创建一个模板函数,它基于模板参数具有两种不同的实现。第一种是特定于I/O操作符的,第二种则适用于任何一般情况。
template <typename T>
typename std::enable_if<
            std::is_same<decltype(std::setw), T>::value>::type
foo(T& t){
        cout << "enabled" << std::endl;
}

template <typename T>
void foo(T& t){
        cout << "Normal" << std::endl;
}

template <typename... Ts>
void foohelper(Ts... t){
        foo(t...);
}
int main(){
        foohelper(std::setprecision(3)); // It should print enabled, but printing Normal
}

目前它没有执行我想要实现的功能。我该如何解决?


1
std::set和std::setprecision不是相同的类型(同时std::setprecision(3)也不是与std::setprecision相同的类型)。如果您能更详细地阐述问题,我们可以给您一个答案! - bendervader
是的,我的意思是std::setw。它们实际上是不同的,在这里查看源代码 http://cs.brown.edu/~jwicks/libstdc++/html_user/iomanip-source.html 他们返回一个结构体,但每个结构体的名称都不同! 尝试使用以下内容进行验证 std::cout << typeid(decltype(std::setw)).name() << std::endl; std::cout << typeid(decltype(std::setprecision)).name() << std::endl; - bendervader
3个回答

5

你不需要使用 enable_if - 只需在主定义下方特化模板 template foo

template <>
void foo<decltype(std::setw(0))>(decltype(std::setw(0))& t)
{
        cout << "enabled" << std::endl;
}

谢谢,它有效了,但我不明白为什么我必须在main函数下面保留定义。 - AAA
这看起来不错,但在这种情况下,专业化可以被简单的重载所替代吗? - Guillaume Racicot
@AAA:我的措辞不够准确,我指的是在模板定义下方而非main()函数下方,也就是在template <typename T> void foo(T & t) ...下方。特化之前应该先看到模板。 - Tony Delroy
@GuillaumeRacicot:对于这个特定的调用,是的,但是假设一个函数template <typename T> f包含获取指向函数的指针的代码,例如auto p = &foo<T>;,它会忽略重载;虽然这不是一个实际问题。 - Tony Delroy

2
这些函数的返回类型是未指定的。这意味着您不能使用type_traits来进行比较。以下是一个libcxx的例子:
// Definitions omitted
T2 setiosflags (ios_base::fmtflags mask);
T3 setbase(int base);
template<charT> T4 setfill(charT c);
T5 setprecision(int n);
T6 setw(int n);

T5T6等是指内部结构__imo_t4等,这些结构实际上负责机器人的工作。由于它们都有不同的返回类型,因此这些函数也具有不同的类型,所以它们都是不同的。


2
你可以使用未指定类型。一个很好的例子是std::bind的返回类型。 - Guillaume Racicot
@GuillaumeRacicot std::bind和比较std::setwstd::setprecision有什么关系? - user6323422
这些踩票真是令人沮丧。这个答案甚至无法编译,而这个答案则没有涉及到一般情况。 - user6323422
5
你说由于这些函数的返回类型不确定,所以无法使用type_traits进行比较,但这并不正确。实际上,std::bind和lambda都有未指定的类型,并且我通常在type_traits和模板参数中使用它们。我甚至通过调用它们的operator()来使用它们的类型。因此,声称由于类型不确定而不能在类型特征中使用它们是不正确的。 - Guillaume Racicot
@GuillaumeRacicot:lambda 函数和 std::bind 的未指定返回类型应该具有一些与 operator() 相似的接口,使用未指定类型作为此特征将导致未指定的特征。T2T3 可能是相等的类型,也可能不是,因此对其中一个进行特化也可能适用于另一个,而对两者进行特化可能会编译或可能不会。 - Jarod42

2

您可以通过比较函数 std::setprecision 的返回类型来使用 SFINAE。由于返回类型是未指定的,因此您需要使用 decltypestd::declval

以下是解决方案:

template <typename T, typename std::enable_if<std::is_same<decltype(std::setprecision(std::declval<int>())), typename std::decay<T>::type>::value, int>::type = 0>
void foo(T&& t) {
    cout << "enabled" << std::endl;
}

在这里,我将T&更改为T&&,以便它接受任何类型的引用。这样就可以避免使用foohelper

此外,在类型比较中,我衰减了类型T,因为如果其中一个类型是引用类型,那么比较将无法工作,因为T&T不同。使用std::decay将有效地删除引用。

请注意,我将SFINAE结构移动到模板声明内部。这是为了让SFINAE远离返回类型。默认为0的非类型模板参数的使用不会影响函数的使用,并确保没有人可以有效地绕过SFINAE检查。

编辑:

在您的情况下,您可以将std::declval<int>()替换为0。我只是想展示如何使用此工具替换实际值。有很多情况下,您不能拥有值。


非常感谢。但我必须使用foohelper,这是我的应用程序编写的方式。 - AAA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接