Reputation: 47620
I have template class Reader
template<typename T>
class Reader{
typedef T type;
};
Special implementations (derrived classes) have methods with signature
T read(IStream&, any number of arguments, zero possible)
i.e class IntegerReader
public functions:
template <typename T>
class IntegerReader : public Reader<T>{
public:
T read(IStream& stream);
T read(IStream& stream, T min, T max);
T read(IStream& stream, T min, T max, std::string name);
}
Now I want to create a wrapper, that will allow me create another reader, with will call some reader with arguments.
I've tried this:
template <typename T, typename... Args>
class ParametrizedReader : public Reader<typename T::type> {
T reader;
Args... args;
ParametrizedReader(T reader, Args... args):reader(reader), args(args){
}
typename T::type read(IStream& stream){
return reader.read(args..., stream);
}
};
testlib/readerWrapper.hpp:7:6: error: expected unqualified-id before ‘...’ token
testlib/readerWrapper.hpp: In constructor ‘ParametrizedReader<T, Args>::ParametrizedReader(T, Args ...)’:
testlib/readerWrapper.hpp:8:61: error: class ‘ParametrizedReader<T, Args>’ does not have any field named ‘args’
testlib/readerWrapper.hpp: In member function ‘typename T::type ParametrizedReader<T, Args>::read(IStream&)’:
testlib/readerWrapper.hpp:12:22: error: ‘args’ was not declared in this scope
this:
template <typename T, typename... Args>
class ParametrizedReader : public Reader<typename T::type> {
std::function<T()> lambda;
ParametrizedReader(T reader, Args... args){
lambda = [=](IStream& stream){
reader.read(stream, args...);
};
}
typename T::type read(IStream& stream){
return lambda(stream);
}
};
testlib/readerWrapper.hpp:9:24: error: parameter packs not expanded with ‘...’:
testlib/readerWrapper.hpp:9:24: note: ‘args’
testlib/readerWrapper.hpp:9:28: error: expansion pattern ‘args’ contains no argument packs
and this:
template <typename T, typename... Args>
class ParametrizedReader : public Reader<typename T::type> {
std::function<T()> lambda;
ParametrizedReader(T reader, Args... args){
lambda = [reader, args...](IStream& stream){
reader.read(stream, args...);
};
}
typename T::type read(IStream& stream){
return lambda(stream);
}
};
testlib/readerWrapper.hpp:8:25: error: expected ‘,’ before ‘...’ token
testlib/readerWrapper.hpp:8:25: error: expected identifier before ‘...’ token
testlib/readerWrapper.hpp:8:28: error: parameter packs not expanded with ‘...’:
testlib/readerWrapper.hpp:8:28: note: ‘args’
Compilation errors given by g++-4.7
While I'm not sure the first example is correct and should compile, I believe the second and third should.
I found this bug, which seems to be not fixed.
Is there workarounds, how can I do what I want?
Upvotes: 4
Views: 1285
Reputation: 393049
This reminds me of a known bug: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=41933 It affects at least gcc 4.6.2 and 4.7.x series.
Clang 3.1 has no problems dealing with variadics in lambda captures.
Perhaps you can go the old fashioned route and store the stuff off into a tuple: http://liveworkspace.org/code/7d4347021aaf004489591e78654f0233
#include <tuple>
#include <vector>
#include <string>
////////////////////////////////////
template<int ...> struct seq { };
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> { };
template<int ...S> struct gens<0, S...> { typedef seq<S...> type; };
////////////////////////////////////
template<typename T>
struct Reader
{
typedef T type;
};
//Special implementations (derrived classes) have methods with signature
struct IStream {};
template <typename T>
class IntegerReader : public Reader<T>
{
public:
T read(IStream& stream);
T read(IStream& stream, T min, T max);
T read(IStream& stream, T min, T max, std::string name);
};
template <typename T, typename... Args>
class ParametrizedReader : public Reader<typename T::type>
{
T _reader;
std::tuple<Args...> _args;
public:
ParametrizedReader(T reader, Args... args)
: _reader(reader), _args(std::forward<Args>(args)...)
{
}
typename T::type read(IStream& stream)
{
callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
void callFunc(seq<S...>)
{
func(std::get<S>(_args) ...);
}
};
template <typename T, typename... Args>
ParametrizedReader<T, Args...> make_parameterized_reader(T reader, Args... args)
{
return ParametrizedReader<T, Args...>(reader, std::forward<Args>(args)...);
}
int main(int argc, const char *argv[])
{
Reader<char> reader;
auto pr = make_parameterized_reader(reader, "stuff", 3.14, std::string("you can think of"), std::vector<int> { 1,2,3 });
}
Upvotes: 3
Reputation: 131799
You can work around this problem by binding the arguments to the lambda, and not capturing them.
ParametrizedReader(T reader, Args... args){
lambda = std::bind(
[=](IStream& stream, Args... as){
reader.read(stream, as...);
}, args...);
}
Though you might want to do as @Alexandre says and not parameterize the class template on the exact argument types:
template <typename T>
class ParametrizedReader : public Reader<typename T::type> {
std::function<T()> lambda;
template<typename... Args>
ParametrizedReader(T reader, Args... args){
lambda = std::bind(
[=](IStream& stream, Args... as){
reader.read(stream, as...);
}, args...);
}
// ...
};
(Note: Untested.)
What might also work is to just drop std::bind
in the second snippet and try like your second or third solution, this time with a variadic function template. Maybe it works, who knows.
Upvotes: 4
Reputation: 56956
I'd drop the parameter pack from the class template, use perfect forwarding and std::bind.
template <typename T>
class ParametrizedReader : public Reader<typename T::type>
{
std::function<T()> lambda;
template <typename... Args>
ParametrizedReader(T reader, Args&&... args)
{
using namespace std::placeholders;
lambda = std::bind(
std::mem_fn(&Reader::read),
_1,
std::forward<Args>(args)...);
}
typename T::type read(IStream& stream)
{
return lambda(stream);
}
};
Upvotes: 0