Подтвердить что ты не робот

Почему порядок описания важен для передачи указателя функции-члена в качестве аргумента шаблона?

Посмотрите на этот код:

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.

Однако, когда я меняю порядок foo и type_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) { }
};

Однако это имеет эффект в этом случае. Почему?

4b9b3361

Ответ 1

Это не связано с шаблонами. Вы получаете аналогичную ошибку по адресу:

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

Верно, что класс (стандарт 9.2/2):

считается полным в функциональных телах, аргументы по умолчанию, using-declarations, вводящие наследующие конструкторы (12.9), спецификации исключений и скобки или равные инициализаторы для нестатических членов данных (включая такие вещи во вложенных классах).

Однако определение типа элемента не входит в этот список, поэтому он может использовать только имена, объявленные до этой точки.

Ответ 2

Вывод шаблона происходит до того, как компилятор передал весь класс, а это значит, что компилятор в данный момент не знает foo, чтобы создать экземпляр Testee.

Это связано с тем, почему шаблоны могут работать с объявленными вперед классами в качестве параметров шаблона, но не с классами, которые объявлены позже в коде.

Подробнее об этом читайте здесь: Выделение шаблона класса

Чтобы создать экземпляр шаблона класса, каждый шаблонный аргумент должен быть известен, но не каждый параметр шаблона должен быть указан.

UPDATE

Я немного исследовал, почему ваш пример с функцией и параметром по умолчанию компилируется. Оказывается, что это действительно С++ и исключение для объявления параметров по умолчанию. Эти параметры по умолчанию, вероятно, анализируются компилятором после того, как он знает весь класс, чтобы сделать эту работу.

Нестатические члены класса не допускаются по умолчанию (даже если они не оцениваются), за исключением случаев, когда они используются для формирования указателя на член или в выражении доступа к члену.

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

Ответ 3

Обычно порядок объявления в определении класса не имеет эффектов.

Это довольно преувеличение. Допустимо использование нескольких объявлений объявлений, которые появляются позже в определении класса, насколько мне известно:

  • аргументы по умолчанию (как вы упомянули, но не аргументы шаблона по умолчанию)
  • используется внутри тела функции, функции-try-block или инициализатора элемента
  • инициализаторы в классе (С++ 11 или новее)

Кроме того, как уже упоминалось, порядок членов данных влияет на порядок строительства и уничтожения. Кроме того, переупорядочение материалов между единицами перевода может неожиданно вызвать нарушение ODR.