为什么声明顺序对于将成员函数指针作为模板参数传递很重要?

时间:2017-06-09 11:01:25

标签: c++ templates member-pointers

看看这段代码:

template <typename T, void (T::*pfn)()> struct Testee {};

class Tester
{
private:
    void foo() {}
public:
    using type_t = Testee<Tester, &Tester::foo>;    
};

它成功编译g++ -std=c++14 -Wall -Wextra

但是,当我更改footype_t的顺序时,会发生错误:

$ cat test.cpp
template <typename T, void (T::*pfn)()> struct Testee {};

class Tester
{
public:
    using type_t = Testee<Tester, &Tester::foo>;
private:
    void foo() {}
};

int main()
{
}

$ g++ -std=c++14 -Wall -Wextra -pedantic test.cpp
test.cpp:6:36: error: incomplete type ‘Tester’ used in nested name specifier
     using type_t = Testee<Tester, &Tester::foo>;
                                    ^
test.cpp:6:47: error: template argument 2 is invalid
     using type_t = Testee<Tester, &Tester::foo>;
                                               ^

通常,类定义中的声明顺序对名称解析没有影响。例如:

struct A // OK
{
    void foo(int a = val) { }
    static constexpr const int val = 42;
};

struct B // OK
{
    static constexpr const int val = 42;
    void foo(int a = val) { }
};

然而,它在这种情况下有效。为什么呢?

3 个答案:

答案 0 :(得分:32)

这与模板无关。你得到类似的error

class Tester
{
public:
    using type_t = decltype(&Tester::foo);
private:
    void foo() {}
};

一个类是(标准9.2 / 2):

  

在职能机构内被视为完整,   默认参数, using-declarations 引入继承构造函数(12.9), exception-specifications ,以及    brace-or-equal-initializers 用于非静态数据成员(包括嵌套类中的这类内容)。

但是,成员类型的定义不在该列表中,因此它只能使用在该点之前声明的名称。

答案 1 :(得分:4)

模板推导发生在编译器传递整个类之前,这意味着当时编译器还不知道 foo ,以便实例化 < EM>测试对象 即可。

这与模板可以使用前向声明的类作为模板参数但不能使用稍后在代码中声明的类的原因有关。

在这里阅读更多相关信息: Class Template Deduction

为了实例化一个类模板,必须知道每个模板参数,但不是必须指定每个模板参数。

<强>更新

我做了一些关于为什么你的函数和默认参数的例子编译的研究。原来这是有效的C ++和默认参数声明的例外。这些默认参数可能在编译器知道整个类之后解析,以使其工作。

默认参数中不允许使用非静态类成员(即使它们未被计算),除非用于形成指向成员的指针或成员访问表达式。

http://en.cppreference.com/w/cpp/language/default_arguments

答案 2 :(得分:0)

  

通常,类定义中的声明顺序没有效果。

这很夸大其词。根据我的知识,允许在类定义中稍后出现的声明的一些用法:

  • 默认参数(如您所述;但不是默认模板参数)
  • 在函数体,function-try-block或成员初始化程序中使用
  • 类内初始值设定项(C ++ 11或更高版本)

另外,如上所述,数据成员的顺序影响构造和销毁顺序。此外,翻译单位之间的重新排序可能会令人惊讶地导致ODR违规。