std::function 作为模板参数

Posted

技术标签:

【中文标题】std::function 作为模板参数【英文标题】:std::function as template parameter 【发布时间】:2014-01-03 22:52:35 【问题描述】:

我目前有一个map<int, std::wstring>,但为了灵活性,我希望能够分配一个 lambda 表达式,将 std::wstring 作为映射中的值返回。

所以我创建了这个模板类:

template <typename T>
class ValueOrFunction

private:
    std::function<T()> m_func;
public:
    ValueOrFunction() : m_func(std::function<T()>()) 
    ValueOrFunction(std::function<T()> func) : m_func(func) 
    T operator()()  return m_func(); 
    ValueOrFunction& operator= (const T& other)
    
        m_func = [other]() -> T  return other; ;
        return *this;
    
;

并像这样使用它:

typedef ValueOrFunction<std::wstring> ConfigurationValue;
std::map<int, ConfigurationValue> mymap;

mymap[123] = ConfigurationValue([]() -> std::wstring  return L"test"; );
mymap[124] = L"blablabla";
std::wcout << mymap[123]().c_str() << std::endl; // outputs "test"
std::wcout << mymap[124]().c_str() << std::endl; // outputs "blablabla"

现在,我不想使用构造函数来包装 lambda,所以我决定添加第二个赋值运算符,这次是 std::function

ValueOrFunction& operator= (const std::function<T()>& other)

    m_func = other;
    return *this;

这是编译器开始抱怨的地方。 mymap[124] = L"blablabla";这行突然出现这个错误:

错误 C2593:“运算符 = 不明确”

IntelliSense 提供了更多信息:

多个运算符“=”匹配这些操作数:函数 "ValueOrFunction::operator=(const std::function &other) [with T=std::wstring]" 函数 "ValueOrFunction::operator=(const T &other) [with T=std::wstring]" 操作数类型为:ConfigurationValue = 常量 wchar_t [10] c:\projects\beta\CppTest\CppTest\CppTest.cpp 37 13 CppTest

那么,我的问题是,为什么编译器不能区分std::function&lt;T()&gt;T?我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

基本问题是std::function 有一个贪婪的隐式构造函数,它会尝试转换任何东西,并且只会在正文中编译失败。所以如果你想用它重载,要么不允许转换为替代,你需要禁用可以转换为替代的东西调用std::function重载。

最简单的技术是标签调度。创建一个贪婪的operator= 并设置为完美转发,然后手动分派到带有标签的assign 方法:

 template<typename U>
 void operator=(U&&u)
   assign(std::forward<U>(u), std::is_convertible<U, std::wstring>());
 
 void assign(std::wstring, std::true_type /*assign_to_string*/);
 void assign(std::function<blah>, std::false_type /*assign_to_non_string*/);

基本上我们正在做手动重载解决方案。

更高级的技术:(可能不需要)

另一种方法是使用 SFINAE 限制 std::function = 被调用的参数是有效的,但这更麻烦。

如果您有多种不同的类型与您的std::function 竞争,您不得不手动调度所有这些类型。解决这个问题的方法是测试您的类型 U 是否可以调用并且结果可转换为 T,然后对其进行标记调度。将非std::function 重载粘贴到替代分支中,并让通常更传统的重载发生在其他所有内容上。

有一个细微的区别,即可转换为 std::wstring 和可调用返回可转换为 T 的类型最终被分派到与上述原始简单解决方案不同的重载,因为使用的测试实际上并不是互斥的.对于 C++ 重载的完整手动模拟(针对 std::functions 的愚蠢进行了更正),您需要使 那个 的大小写模棱两可!

最后要做的高级事情是使用auto 和尾随返回类型来提高其他代码检测您的= 是否有效的能力。就个人而言,除非迫不得已,否则我不会在 C++14 之前这样做,除非我正在编写一些严肃的库代码。

【讨论】:

我在考虑 SFINAE 是否有意义,我选择不建议它过于复杂。这里练习的标签调度是一个很好的干净解决方案+1(尽管它值得更多)【参考方案2】:

std::functionstd::wstring 都有转换运算符,可以采用您传递的文字宽字符串。在这两种情况下,转换都是用户定义的,因此转换顺序具有相同的优先级,从而导致歧义。这是错误的根本原因。

【讨论】:

以上是关于std::function 作为模板参数的主要内容,如果未能解决你的问题,请参考以下文章

模板函数替换仅在一个参数中未使用std :: function时才起作用

C++11 std::bind函数,std::function函数

std::function 的模板替换失败

C++11:std::function<void()> func;

C++ std::vector 作为 std::function 的参数

无法调用或分配具有右值引用作为参数的 std::function (Visual C++)