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.
This happens because {}
is not an expression and can only be used in limited ways while doing argument deduction, the parameter must have specific forms in order to succeed.
The allowed parameters types that can be used to deduce template parameters when {}
is involved are better expanded in [temp.deduct.call]/1, two of the examples extracted from the cited part of the standard are:
template<class T> void f(std::initializer_list<T>);
f({1,2,3}); // T deduced to int
template<class T, int N> void h(T const(&)[N]);
h({1,2,3}); // T deduced to int
In your example the deduction guide is not used to deduce the T
for {test}
for the same as above.
foo.do_thing(Bar{test});
is your direct option without using additional functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With