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

时间:2018-12-29 20:46:23

标签: c++ templates c++17 template-deduction

我想在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,将来可能会更改(尽管不太可能)。因此,在Bar内构造do_thing很难维护...

2 个答案:

答案 0 :(得分:3)

  

想让do_thing({test})do_thing(test)隐式构造一个Bar并在可能的情况下将其作为参数传递。

不幸的是,当您调用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询问

  

有什么方法可以保留{test}大括号语法吗?也许用initializer_list之类的东西?

是的... 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]); }

这样,您只接受一个元素。

答案 1 :(得分:3)

之所以会发生这种情况,是因为{}不是表达式,只能在进行参数推导时以有限的方式使用,参数必须具有特定的形式才能成功。

[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

在您的示例中,推导指南不像上述一样用于推导T的{​​{1}}。

{test}

是直接选择,而无需使用其他功能。