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

Я пытаюсь написать потокобезопасную оболочку для 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>)'

что действительно имеет смысл.

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

Ответ 1

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

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] указывает правила, используемые здесь, которые возвращаются к С++ 98. В принципе, использование адреса перегруженной и/или шаблонной функции в целом является неоднозначным, но разрешено в определенных контекстах. Инициализация и static_casting являются двумя из этих контекстов, поскольку они предоставляют достаточную информацию о типе для устранения неоднозначности. Это позволяет продолжить вывод аргумента шаблона.

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

Ответ 2

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

Например:

f(1, 2, 3, &std::endl<char, std::char_traits<char>>);
//                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Ответ 3

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, а затем использую << на нем без каких-либо дополнительных ошибок.