I would like to have the following code in c++17:
#include <iostream>
#include <string>
#include <type_traits>
#include <functional>
class Foo;
template<class T>
class Bar {
public:
std::function<T(Foo&)> m_fn;
template<class Fn>
Bar(Fn fn) : m_fn(fn) {};
T thing(Foo &foo) const {
return m_fn(foo);
}
};
template<class Fn>
Bar(Fn) -> Bar<decltype(std::invoke(std::declval<Fn>(),
std::declval<Foo&>()))>;
class Foo {
public:
Foo() {};
template<class T>
std::vector<T> do_thing(const Bar<T> &b) {
std::vector<T> r;
r.push_back(b.thing(*this));
return r;
}
};
std::string test(Foo &) {
return "hello";
}
int main() {
Foo foo = Foo();
// works
std::vector<std::string> s = foo.do_thing(Bar{test});
// cant deduce T parameter to do_thing
std::vector<std::string> s = foo.do_thing({test});
}
But compiling this gives me "couldn't deduce template parameter ‘T’" on the call to do_thing
.
Having do_thing(Bar{test})
fixes this and works fine but equates to some ugly code in the real code equivalent. I would like to have do_thing({test})
or do_thing(test)
implicitly construct a Bar
and pass that as the argument if possible.
I also don't want to forward declare a variable to pass into do_thing
either
Is there some way to guide the inference of template argument T
so that the call to do_thing
can stay clean?
Sorry for the late edit, but the arguments to the Bar constructor are over simplified in the example I included. In reality, there is an extra parameter std::optional<std::string> desc = std::nullopt
and that might change in the future (although unlikely). So constructing the Bar
inside do_thing
would be a bit hard to maintain...
would like to have
do_thing({test})
ordo_thing(test)
implicitly construct aBar
and pass that as the argument if possible.
Unfortunately, when you call do_thing({test})
or do_thing(test)
, test
(or {test}
) isn't a Bar<T>
object. So the compiler can't deduce the T
type and can't construct a Bar<T>
object.
A sort of chicken-and-egg problem.
The best I can imagine is to add, in Foo
, a do_test()
method as follows
template<typename T>
auto do_thing (T const & t)
{ return do_thing(Bar{t}); }
This way you can call (without graphs)
std::vector<std::string> s = foo.do_thing(test);
You get the same result as
std::vector<std::string> s = foo.do_thing(Bar{test});
-- EDIT --
The OP ask
is there any way of preserving the {test} brace syntax? maybe with initializer_list or something?
Yes... with std::initializer_list
template<typename T>
auto do_thing (std::initializer_list<T> const & l)
{ return do_thing(Bar{*(l.begin())}); }
but, this way, you accept also
std::vector<std::string> s = foo.do_thing(Bar{test1, test2, test3});
using only test1
Maybe a little better... another way can be through a C-style array
template <typename T>
auto do_thing (T const (&arr)[1])
{ return do_thing(arr[0]); }
This way you accept only an element.