I'm trying to create a custom std::istream
using Boost's stream
class. It's supposed to be easier than manually creating a std::streambuf
but I've hit a roadblock.
Constructing a stream
requires a source
object. stream
has a set of forwarding constructors that take a list of arguments and forward them to the source's constructor—that's what I want to use.
// Forwarding constructors template<typename U> stream([const] U& u); template<typename U1, typename U2> stream([const] U1& u1, const U2& u2); ... template<typename U1, ..., typename UN> stream([const] U1& u1, const U2& u2, ..., const UN& uN);
Each of these members constructs an instance of
stream
and associates it with an instance of the DeviceT
constructed from the given lists of arguments. TheT
constructors involved must take all arguments by value orconst
reference.
It works great with three arguments:
#include <memory>
#include <boost/iostreams/stream.hpp>
namespace io = boost::iostreams;
class IoSource: public io::source {
public:
IoSource(int foo, int bar, int baz) { }
std::streamsize read(char *buffer, std::streamsize n) { return -1; }
};
int main() {
auto stream = std::make_unique<io::stream<IoSource>>(1, 2, 3);
}
Result:
$ g++ -Wall -Werror -std=gnu++20 test.cpp -o /dev/null
The same program with four arguments fails to compile with a barrage of template errors:
#include <memory>
#include <boost/iostreams/stream.hpp>
namespace io = boost::iostreams;
class IoSource: public io::source {
public:
IoSource(int foo, int bar, int baz, int quux) { }
std::streamsize read(char *buffer, std::streamsize n) { return -1; }
};
int main() {
auto stream = std::make_unique<io::stream<IoSource>>(1, 2, 3, 4);
}
Result:
$ g++ -Wall -Werror -std=gnu++20 test.cpp -o /dev/null
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h: In instantiation of 'typename std::_MakeUniq<_Tp>::__single_object std::make_unique(_Args&& ...) [with _Tp = boost::iostreams::stream<IoSource>; _Args = {int, int, int, int}; typename std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<boost::iostreams::stream<IoSource>, std::default_delete<boost::iostreams::stream<IoSource> > >]':
test.cpp:14:68: required from here
/usr/include/c++/10/bits/unique_ptr.h:962:30: error: no matching function for call to 'boost::iostreams::stream<IoSource>::stream(int, int, int, int)'
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/boost/preprocessor/tuple/elem.hpp:23,
from /usr/include/boost/preprocessor/repetition/enum_binary_params.hpp:19,
from /usr/include/boost/iostreams/detail/forward.hpp:22,
from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'boost::iostreams::stream<Device, Tr, Alloc>::stream(U100&, const U0&, const U1&, typename boost::disable_if<boost::is_same<U0, T> >::type*) [with U100 = int; U0 = int; U1 = int; Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>; typename boost::disable_if<boost::is_same<U0, T> >::type = void]' (near match)
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: conversion of argument 4 would be ill-formed:
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h:962:30: error: invalid conversion from 'int' to 'boost::disable_if_c<false, void>::type*' {aka 'void*'} [-fpermissive]
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| |
| int
In file included from /usr/include/boost/preprocessor/tuple/elem.hpp:23,
from /usr/include/boost/preprocessor/repetition/enum_binary_params.hpp:19,
from /usr/include/boost/iostreams/detail/forward.hpp:22,
from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'boost::iostreams::stream<Device, Tr, Alloc>::stream(const U0&, const U1&, const U2&, typename boost::disable_if<boost::is_same<U0, T> >::type*) [with U0 = int; U1 = int; U2 = int; Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>; typename boost::disable_if<boost::is_same<U0, T> >::type = void]' (near match)
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: conversion of argument 4 would be ill-formed:
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h:962:30: error: invalid conversion from 'int' to 'boost::disable_if_c<false, void>::type*' {aka 'void*'} [-fpermissive]
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| |
| int
In file included from /usr/include/boost/preprocessor/tuple/elem.hpp:23,
from /usr/include/boost/preprocessor/repetition/enum_binary_params.hpp:19,
from /usr/include/boost/iostreams/detail/forward.hpp:22,
from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'template<class U100, class U0> boost::iostreams::stream<Device, Tr, Alloc>::stream(U100&, const U0&, typename boost::disable_if<boost::is_same<U0, T> >::type*) [with U100 = U100; U0 = U0; Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: template argument deduction/substitution failed:
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h:962:30: note: candidate expects 3 arguments, 4 provided
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/boost/preprocessor/tuple/elem.hpp:23,
from /usr/include/boost/preprocessor/repetition/enum_binary_params.hpp:19,
from /usr/include/boost/iostreams/detail/forward.hpp:22,
from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'template<class U0, class U1> boost::iostreams::stream<Device, Tr, Alloc>::stream(const U0&, const U1&, typename boost::disable_if<boost::is_same<U0, T> >::type*) [with U0 = U0; U1 = U1; Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: template argument deduction/substitution failed:
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h:962:30: note: candidate expects 3 arguments, 4 provided
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/boost/preprocessor/tuple/elem.hpp:23,
from /usr/include/boost/preprocessor/repetition/enum_binary_params.hpp:19,
from /usr/include/boost/iostreams/detail/forward.hpp:22,
from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'template<class U100> boost::iostreams::stream<Device, Tr, Alloc>::stream(U100&, typename boost::disable_if<boost::is_same<U0, T> >::type*) [with U100 = U100; Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: template argument deduction/substitution failed:
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h:962:30: note: candidate expects 2 arguments, 4 provided
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/boost/preprocessor/tuple/elem.hpp:23,
from /usr/include/boost/preprocessor/repetition/enum_binary_params.hpp:19,
from /usr/include/boost/iostreams/detail/forward.hpp:22,
from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'template<class U0> boost::iostreams::stream<Device, Tr, Alloc>::stream(const U0&, typename boost::disable_if<boost::is_same<U0, T> >::type*) [with U0 = U0; Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: template argument deduction/substitution failed:
In file included from /usr/include/c++/10/memory:83,
from test.cpp:1:
/usr/include/c++/10/bits/unique_ptr.h:962:30: note: candidate expects 2 arguments, 4 provided
962 | { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/boost/iostreams/stream.hpp:18,
from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'boost::iostreams::stream<Device, Tr, Alloc>::stream(const boost::reference_wrapper<T>&, std::streamsize, std::streamsize) [with Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>; std::streamsize = long int]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate expects 3 arguments, 4 provided
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'boost::iostreams::stream<Device, Tr, Alloc>::stream(Device&, std::streamsize, std::streamsize) [with Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>; std::streamsize = long int]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate expects 3 arguments, 4 provided
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate: 'boost::iostreams::stream<Device, Tr, Alloc>::stream(const Device&, std::streamsize, std::streamsize) [with Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>; std::streamsize = long int]'
144 | BOOST_IOSTREAMS_FORWARD( stream, open_impl, Device,
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/iostreams/stream.hpp:144:5: note: candidate expects 3 arguments, 4 provided
In file included from test.cpp:3:
/usr/include/boost/iostreams/stream.hpp:143:5: note: candidate: 'boost::iostreams::stream<Device, Tr, Alloc>::stream() [with Device = IoSource; Tr = std::char_traits<char>; Alloc = std::allocator<char>]'
143 | stream() { }
| ^~~~~~
/usr/include/boost/iostreams/stream.hpp:143:5: note: candidate expects 0 arguments, 4 provided
What am I doing wrong? I believe I've narrowed this down to the core problem. It seems to be purely an issue with the number of arguments; it doesn't matter if they're values or const
references, nor do their types matter.
Boost doesn't use variadic templates and perfect forwarding to forward arguments. It uses preprocessor macros to simulate varargs, macros which have a maximum arity:
#ifndef BOOST_IOSTREAMS_MAX_FORWARDING_ARITY # define BOOST_IOSTREAMS_MAX_FORWARDING_ARITY 3 #endif
The number of arguments can be increased by defining the macro above before including Boost's headers:
#define BOOST_IOSTREAMS_MAX_FORWARDING_ARITY 4
#include <boost/iostreams/stream.hpp>