在C++17中,为什么类模板和函数模板的指针类型推导明显不一致

In C++17, why is pointer type deduction apparently inconsistent for class templates versus function templates?

本文关键字:类型 指针 不一致 函数模板 C++17 为什么      更新时间:2023-10-16

如果我将下面代码中的class更改为0而不是1,它将使用gcc或clang进行编译。但如果CLASS为1,则两个编译器都将失败(第二次使用bar时(。

#define CLASS 1
#include <utility>
void f(int *a);
template <typename Arg>
#if CLASS
struct bar {
#else
void
#endif
bar(Arg && arg) { f(std::forward<Arg>(arg)); }
#if CLASS
};
#endif
void foo()
{
int dummy;
int *p = &dummy;
#if !CLASS
#define a
#define b
#endif
bar a(p + 0);
bar b(p);
}

https://godbolt.org/g/3hpq2u

作为两个单独的片段,无条件编译:

失败:

#include <utility>
void f(int *a);
template <typename Arg>
struct bar {    
bar(Arg && arg) { f(std::forward<Arg>(arg)); }
};
void foo()
{
int dummy;
int *p = &dummy;
bar a(p + 0);
bar b(p);
}

成功:

#include <utility>
void f(int *a);
template <typename Arg>
void bar(Arg && arg) { f(std::forward<Arg>(arg)); }
void foo()
{
int dummy;
int *p = &dummy;
bar(p + 0);
bar(p);
}
template <typename Arg>
struct bar {
bar(Arg && arg) { f(std::forward<Arg>(arg)); }
};

template <typename Arg>
void bar(Arg && arg) { f(std::forward<Arg>(arg)); }

不是一回事。在函数bar中,您有一个转发引用。这允许您接受左值(p(或右值(p + 0(。

有了结构体bar,就不再有转发引用了。它不再是转发引用的原因是它不再是函数模板参数。当CCD_ 6未知时,CCD_。由于您的T(Args(是已知的(虽然不是真的,但它正在被推导,因此它可以是已知的(,这意味着它不再是转发参考。

这意味着您有一个对类模板类型的右值引用。这对来说效果很好

bar a(p + 0);

因为结果是临时int*(int*&&(,但是具有

bar b(p);

你有一个int*(int*&(,所以你不能将右值引用绑定到它。


如果你想让类的行为像函数一样,你需要像这样的东西

struct bar {
template <typename Arg>
bar(Arg && arg) { f(std::forward<Arg>(arg)); }
};

否则,如果你只需要接受左值和右值,你可以添加重载来处理

template <typename Arg>
struct bar {
bar(Arg & arg) { f(arg); }
bar(Arg && arg) { f(std::move(arg)); }
};  

我不确定您在这里想要实现什么,但这里有一些方法可以获得一个有效的示例。给定:

#include <utility>
void f(int *a);
template <typename Arg>
struct bar {    
bar(Arg && arg) { f(std::forward<Arg>(arg)); }
};

您可以使用显式模板参数规范初始化对象:

void foo()
{
int dummy;
int *p = &dummy;
bar a(p + 0); // Arg is deduced to be int*
bar<int*&> b(p); // Arg is explicitly int*&
}

或者,您可以为模板结构提供用户定义的推导指南(请参阅文档(:

template<typename Arg> bar(const Arg&) -> bar<Arg&>;
void foo()
{
int dummy;
int *p = &dummy;
bar a(p + 0); // Arg is deduced to be int*
bar b(p); // Arg is deduced to be int*&
}

请记住,如果您的结构有任何模板类型的成员,并且您希望能够用引用类型初始化该结构,则必须初始化这些成员:

template <typename Arg>
struct bar {
Arg member; // if Arg is int*& then member has type int*&
bar(Arg && arg) : member(arg) { f(std::forward<Arg>(arg)); }
};

或者,如果你不需要成员有引用类型,你可以这样做:

template <typename Arg>
struct bar {
std::remove_reference_t<Arg> member; // if Arg is int*& then member has type int*
bar(Arg && arg) { f(std::forward<Arg>(arg)); }
};