无法根据隐式构造的参数推导模板参数

Template parameter can't be deduced on implicitly constructed argument

我想在 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});
}

但是编译这个让我在调用 do_thing 时“无法推断模板参数‘T’”。 do_thing(Bar{test}) 解决了这个问题并且工作正常但等同于真实代码等效中的一些丑陋代码。我想让 do_thing({test})do_thing(test) 隐式构造一个 Bar 并尽可能将其作为参数传递。

我也不想转发声明要传递给 do_thing 的变量

是否有某种方法可以指导模板参数 T 的推断,以便对 do_thing 的调用可以保持干净?

编辑:

抱歉编辑晚了,但在我包含的示例中,Bar 构造函数的参数过于简化了。实际上,有一个额外的参数 std::optional<std::string> desc = std::nullopt 并且将来可能会改变(尽管不太可能)。所以在 do_thing 中构建 Bar 会有点难以维护...

would like to have do_thing({test}) or do_thing(test) implicitly construct a Bar and pass that as the argument if possible.

不幸的是,当您调用 do_thing({test})do_thing(test) 时,test(或 {test})不是 Bar<T> 对象。所以编译器不能推断出T类型,也不能构造一个Bar<T>对象。

一种先有鸡还是先有蛋的问题。

我能想到的最好的办法是在Foo中添加一个do_test()方法如下

template<typename T>
auto do_thing (T const & t)
 { return do_thing(Bar{t}); } 

这样调用(无图)

std::vector<std::string> s = foo.do_thing(test);

你得到与

相同的结果
std::vector<std::string> s = foo.do_thing(Bar{test});

-- 编辑--

OP 问

is there any way of preserving the {test} brace syntax? maybe with initializer_list or something?

是的... std::initializer_list

template<typename T>
auto do_thing (std::initializer_list<T> const & l)
{ return do_thing(Bar{*(l.begin())}); }

但是,这样,您也接受了

std::vector<std::string> s = foo.do_thing(Bar{test1, test2, test3});

仅使用 test1

也许好一点...另一种方法可以通过 C 风格的数组

template <typename T>
auto do_thing (T const (&arr)[1])
 { return do_thing(arr[0]); }

这样你只接受一个元素。

这是因为{}不是表达式,在做参数推导时只能以有限的方式使用,参数必须有特定的形式才能成功。

涉及{}时可用于推导模板参数的允许参数类型在[temp.deduct.call]/1中得到了更好的扩展,提取的两个示例来自标准的引用部分是:

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

在您的示例中,推导指南未用于推导 {test}T 与上述相同。

foo.do_thing(Bar{test});

是不使用附加功能的直接选项。