Есть ли какой-нибудь трюк, который позволил бы мне передать манипуляторы потока функции шаблона с переменным числом аргументов?

Я пытался написать потокобезопасную оболочку для std::cout и решил, что сейчас самое время изучить некоторые вариативные шаблоны.

Вот так.

Затем, когда я подумал, что понял правильно, я заметил, что это не работает с std::endl.

Возьмите этот код:

template <typename... P>
void f(P...){}

int main()
{
    f(1,2,3,std::endl);
}

Когда вы пытаетесь его скомпилировать, GCC очень глупо жалуется:

main.cpp:18:19: error: too many arguments to function 'void f(P ...) [with P = {}]'

Когда вы попробуете это с обычным шаблоном, вы получите

main.cpp:22:13: error: no matching function for call to 'f(<unresolved overloaded function type>)'

что на самом деле имеет смысл.

Для меня это не большая проблема, я могу сделать это по-другому, но мне бы очень хотелось знать, есть ли способ обойти это ограничение.


person user697683    schedule 15.06.2013    source источник


Ответы (3)


Вместо явных шаблонных аргументов, предложенных Энди Проулом, я рекомендую вывод шаблонных аргументов:

C:\Temp>type meow.cpp
#include <iostream>
#include <utility>
using namespace std;

void Print() { }

template <typename T, typename... Rest> void Print(T&& t, Rest&&... rest) {
    cout << forward<T>(t);
    Print(forward<Rest>(rest)...);
}

int main() {
    ostream& (* const narrow_endl)(ostream&) = endl;

    Print("Hello, world!", narrow_endl, "I have ", 1729, " cute fluffy kittens.",
        static_cast<ostream& (*)(ostream&)>(endl)
    );
}

C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp
meow.cpp

C:\Temp>meow
Hello, world!
I have 1729 cute fluffy kittens.

N3690 13.4 [over.over] определяет используемые здесь правила, восходящие к C++98. По сути, получение адреса перегруженной и/или шаблонной функции в целом неоднозначно, но разрешено в определенных контекстах. Инициализация и static_casting — два из этих контекстов, потому что они предоставляют достаточную информацию о типе для устранения неоднозначности. Это позволяет нормальному выводу аргументов шаблона.

Явные аргументы шаблона очень заманчивы, но они могут взорваться самыми разными способами. Маловероятно, что std::endl когда-либо будет изменен таким образом, что здесь будут нарушены явные аргументы шаблона, но я действительно не рекомендую их (кроме случаев, когда что-то специально разработано для них, например, вперед и make_shared).

person Stephan T. Lavavej    schedule 15.06.2013
comment
Значит, нет возможности написать что-то, что позволило бы пользователю использовать модификаторы по их обычным именам, а не по таким вещам, как узкий_endl? - person user697683; 15.06.2013
comment
@user697683 user697683, если вы собираетесь написать функцию печати, обертывающую ostream, вы также можете ввести функциональность оболочки для форматирования (т. Е. Не разрешать использовать манипуляторы ostream в своем интерфейсе и предоставлять альтернативы). Но тогда вы получите некоторый printf, реализованный поверх ostream, который, скорее всего, реализован поверх C printf. - person rubenvb; 17.06.2013

Проблема в том, что такие манипуляторы, как std::endl, являются шаблонами функций. Следовательно, вы должны явно указать, какую специализацию этого шаблона функции вы хотите передать (в противном случае вывод типа невозможен).

Например:

f(1, 2, 3, &std::endl<char, std::char_traits<char>>);
//                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
person Andy Prowl    schedule 15.06.2013

template функции не являются функциями, а std::endl является template функцией.

Вы не можете передать функцию template. Однако вы можете передать объект функции, который представляет набор перегрузок. Написать такой функтор довольно просто:

struct endl_overloadset {
  template<typename... Args>
  auto operator()(Args&&...args)const
    ->decltype(std::endl( std::forward<Args>(args) ) )
    { return ( std::endl( std::forward<Args>(args) ) ) };

  template<typename T,typename=typename std::enable_if<\
    std::is_same< decltype(static_cast<T>( std::endl )), T >::value\
  >::type>\
  operator T() const { return std::endl; }
};

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

#define RETURNS(X) ->decltype(X) { return (X); } // C++11 lacks non-lambda return value deduction
#define OVERLOAD_SET(F) struct {\
  template<typename... Args>\
  auto operator()(Args&&...args)const\
    RETURNS( F( std::forward<Args>(args)... ) )\
  template<typename T,typename=typename std::enable_if<\
    std::is_same< decltype(static_cast<T>( F )), T >::value\
  >::type>\
  operator T() const { return F; }\
}
static OVERLOAD_SET(std::endl) Endl;

затем передайте Endl своему f, и вызов Endl(Blah) завершится выполнением std::endl(Blah). Точно так же присвоение Endl переменной или передача ее методу в основном аналогичны присвоению std::endl переменной или передаче ее методу (относительно разрешения перегрузки).

К сожалению, OVERLOAD_SET нельзя использовать внутри функции, так как локальные типы не могут иметь template методов. Если бы его можно было использовать внутри функции, то:

f(1,2,3, OVERLOAD_SET(std::endl)() );

сделал бы то, что ты хотел. Но это будет язык, на котором вы хотите программировать, а не тот язык, который у вас есть. (Еще лучше было бы предложение @ Xeo разрешить автоматическую генерацию функторов набора перегрузок с использованием некоторого случайного дальнейшего злоупотребления синтаксисом [], а не полагаться на макросы).

Живой пример, где я передаю свой endl_functor методу print, а затем использую << без лишних слов.

person Yakk - Adam Nevraumont    schedule 16.06.2013
comment
Обратите внимание, что такой трансформированный манипулятор больше нельзя использовать как настоящий: std::cout << OVERLOAD_SET(std::endl){}; (или даже std::cout << []std::endl;) не будет компилироваться, потому что эти два манипулятора создают не нормальные функции, а объекты-функции. - person Xeo; 16.06.2013
comment
@xeo operator T добавлено и проверено! endl_functor теперь преобразуется в те же вещи, что и std::endl. - person Yakk - Adam Nevraumont; 17.06.2013