Search code examples
c++boost

Boost.Iostreams stream can't forward 4 constructor arguments


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 Device T constructed from the given lists of arguments. The T constructors involved must take all arguments by value or const 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 1.78
  • g++ (Debian 10.2.1-6) 10.2.1 20210110

Solution

  • 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:

    boost/iostreams/detail/config/limits.hpp
    #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>