2015-01-16 3 views
5

В следующем коде:VARIADIC вспомогательная функция с частичным аргументом пакета

#include <iostream> 

struct Base { 
    virtual ~Base() = default; 
    template <typename T, typename... Args> void helper (void (T::*)(Args..., int), Args...); 
    void bar (int n) {std::cout << "bar " << n << std::endl;} 
}; 

struct Derived : Base { 
    void baz (double d, int n) {std::cout << "baz " << d << ' ' << n << std::endl;} 
}; 

template <typename T, typename... Args> 
void Base::helper (void (T::*f)(Args..., int), Args... args) { 
    // A bunch on lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(args..., n); 
    // ... 
} 

int main() { 
    Base b; 
    Derived d; 
    b.helper(&Base::bar); // GCC 4.8.1 will accept this, Visual Studio 2013 won't. 
    d.helper<Derived, double>(&Derived::baz, 3.14); // Visual Studio 2013 will accept this, GCC 4.8.1 won't 
} 

Я не могу получить ни GCC4.8.1 или VS2013 скомпилировать обе строки выше. Они будут компилировать только один, но не другой (и они не согласны с тем, какая строка правильная и неправильная). В сообщении об ошибке указано, что вычет шаблона не был выполнен обоими компиляторами. Так что же вообще не так? Я поставил все параметры шаблона в последней строке (что, как я думал, будет выводимым), но GCC все же не может быть выведено, хотя VS может. Однако VS не может вывести аргументы шаблона для строки b.foo(&Base::bar);, когда я поместил для этого аргументы шаблона, но GCC может вывести их без аргументов шаблона. Здесь совершенно сбиты с толку. Оба компилятора прослушиваются здесь? Любое исправление возможно на стороне программиста?

+0

Что-то говорит мне, что обе строки недействительны, но я пока не могу объяснить причину. – Barry

+0

@Barry. Надеюсь, ты прав. Затем выяснение правильных двух строк в main() решит проблему, не беспокоясь о какой-либо ошибке со стороны компиляторов. – prestokeys

+0

Вы получаете совершенную переадресацию неправильно: двойной вычет с одним переадресованным. Я считаю, что одно использование невозможно. – Yakk

ответ

2

Я бы не написать первый аргумент как указатель на функцию-член вообще.

В вашем конкретном случае потребовалось поместить первый Args... в невыводимый контекст - и стандарт ясен как грязь о том, что должно произойти после этого, особенно учитывая правило в [temp.deduct.call]/p1, что

Когда появляется параметр функции пакета в не выводится контексте (14.8.2.5), тип этого параметра пакет никогда не выводится.

Я не знаю, каковы последствия этого правила, когда вы пишете void (T::*)(typename identity<Args>::type..., int). Компиляторы тоже не согласны друг с другом.

Даже в нормальном случае, вам придется написать несколько 12 перегрузок, чтобы соответствовать всем возможным формам функций члена указателей (4 возможных резюме-спецификатор-SEQ сек раза 3 возможных реф-классификаторы). В вашем случае, вероятно, безопасно пропустить некоторые (например, volatile и &&), но это все еще раздражает дублирование кода. Кроме того, если вы используете Args... дважды в выведенных контекстах, они выводятся независимо, а выводимые типы должны точно совпадать, что может стать беспорядочным для конечного пользователя. (? std::max(1, 2.5), кто)

Вместо этого, я бы просто написать его как указатель на член:

template <typename T, typename... Args, typename R> 
void Base::helper (R T::*f, Args... args) { 
    // A bunch of lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(args..., n); 
    // ... 
} 

R T::* спичек все указатели на членов; когда вы передаете указатель на функцию-член, R выводится как тип функции. Если вы хотите обеспечить выполнение R-must-a-function, вы можете static_assert по телефону std::is_function<R>::value.

Demo.

+0

Поздний ответ, но это показывает окончательное хорошее использование редко используемых указателей для членов данных. – prestokeys

+0

Что касается идеальной проблемы с пересылкой? Пусть 'Derived' имеют перегрузки' void baz (double & d, int n) {std :: cout << "baz, double &\n";}' и 'void baz (double && d, int n) {std :: cout <<" baz, double &&\n";} '. Как позаботиться об этом? – prestokeys

5

Я думаю, что оба вызова недействительны, потому что оба связаны с невыводимым контекстом. От §14.8.2.5:

В не-выведены контексты:

- [..]

- параметр функции пакета, что не происходит в конце параметра декларирование -list.

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

Если у вас есть void (T::*f)(Args..., int), то есть в невыводимом контексте, потому что пакет параметров функции внутри функции не встречается в конце. Тот факт, что список аргументов указателя на член не выводится, делает вызов всей функции не выведенным. Таким образом, этот вызов не может быть выведено:

b.helper(&Base::bar); 

Для второго, хотя это выглядит, хотя вы явно указать Args..., аргумент void (T::*f)(Args..., int) еще в не выводится контексте, так что компилятор не имеет возможности узнать, если большеArgs необходимо.

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

template <typename T, typename... Args> 
void foo (void (T::*)(typename identity<Args>::type..., int), Args...); 

Таким образом, обе эти линии компиляции:

b.helper(&Base::bar); 
d.helper<Derived, double>(&Derived::baz, 3.14); 

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

+0

Paging @Columbo :) – Barry

+0

Я не уверен, что я убежден. 'Args ...' уже находится в невыводимом контексте в 'void (T :: * f) (Args ..., int)', поэтому зачем помещать его в другой слой не выводимой контекстной справки вообще ? –

+0

@ T.C. Гектометр Я просто совершенно не прав здесь? – Barry

6

Пакет параметров должен быть помещен в конце списка параметров, чтобы автоматически выводиться.

Компилятор не может вывести (Args..., int) из списка параметров, используйте (int, Args...) вместо этого и программа скомпилирует.

#include <iostream> 

struct Base { 
    virtual ~Base() = default; 
    template <typename T, typename... Args> void helper (void (T::*)(int, Args...), Args...); 
    void bar (int n) {std::cout << "bar " << n << std::endl;} 
}; 

struct Derived : Base { 
    void baz (int n, double d) {std::cout << "baz " << d << ' ' << n << std::endl;} 
}; 

template <typename T, typename... Args> 
void Base::helper (void (T::*f)(int, Args...), Args... args) { 
    // A bunch on lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(n, args...); 
    // ... 
} 

int main() { 
    Base b; 
    Derived d; 
    b.helper(&Base::bar); 
    d.helper<Derived, double>(&Derived::baz, 3.14); 
} 

Если вы должны положить int в конце списка параметров, вы можете использовать identity трюк, как сказал @Barry.

Реализация Barebone identity может быть столь же просто, как:

template<typename T> 
struct identity { 
    typedef T type; 
}; 

Затем вы можете вручную вывести типы параметров:

template <typename T, typename... Args> 
void Base::helper (void (T::*f)(typename identity<Args>::type..., int), typename identity<Args>::type... args) { 
    // A bunch on lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(args..., n); 
    // ... 
} 

b.helper<Base>(&Base::bar); 
d.helper<Derived, double>(&Derived::baz, 3.14); 
+0

Nice легко исправить. Большое спасибо. – prestokeys

+0

Я все еще удивляюсь, если совершенные аргументы переадресации ... по-прежнему неправы здесь (это частичный пакет аргументов). Хотя я прислушаюсь к советам Якка, чтобы не делать этого. Что говорят другие об этом? Все кажется прекрасным сейчас с совершенной пересылкой (проверено), но я не знаю, действительно ли это нормально или нет. – prestokeys

Смежные вопросы