Есть ли какой-то трюк, который позволил бы мне передать потоковые манипуляторы в функцию вариационного шаблона?
Я пытаюсь написать потокобезопасную оболочку для 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
, а затем использую <<
на нем без каких-либо дополнительных ошибок.