Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template parameter can't be deduced on implicitly constructed argument

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?

Edit:

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...

like image 494
Javyre Avatar asked Dec 29 '18 20:12

Javyre


2 Answers

would like to have do_thing({test}) or do_thing(test) implicitly construct a Bar 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.

like image 165
max66 Avatar answered Nov 15 '22 04:11

max66


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.

like image 31
Jans Avatar answered Nov 15 '22 02:11

Jans