【问题标题】:Template parameter type deduction for pointer vs. nullptr_t指针与 nullptr_t 的模板参数类型推导
【发布时间】:2020-08-30 21:20:50
【问题描述】:

考虑以下 C++ 程序:

#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
#include <utility>

namespace {
template <typename Result, typename... Arg>
Result call_fn(std::function<Result(Arg...)> fn, Arg&&... arg) {
    return fn(std::forward<Arg>(arg)...);
}

std::string test_fn(int, std::string*) {
    return "hello, world!";
}
}

int main(int, char**) {
    std::cout << call_fn(std::function(test_fn), 0, nullptr) << "\n";

    return EXIT_SUCCESS;
}

这是一个人为的例子,但我在尝试实现与std::make_unique 模糊相似的东西时遇到了同样的问题。编译此程序时,出现以下错误:

$ clang++ -std=c++17 -Wall -Weverything -Werror -Wno-c++98-compat call_fn.cpp
call_fn.cpp:19:18: error: no matching function for call to 'call_fn'
    std::cout << call_fn(std::function(test_fn), 0, nullptr) << "\n";
                 ^~~~~~~
call_fn.cpp:9:8: note: candidate template ignored: deduced conflicting types for parameter 'Arg' (<int, std::__cxx11::basic_string<char> *> vs. <int, nullptr_t>)
Result call_fn(std::function<Result(Arg...)> fn, Arg&&... arg) {
       ^
1 error generated.

问题似乎是指针参数的类型推断为nullptr_t,而不是std::string*。我可以通过添加static_cast 或通过将模板参数显式指定为call_fn 来“解决”这个问题,但我觉得这种冗长程度令人反感。

有没有办法修改call_fn的定义,使得类型推导对指针参数更有效?

【问题讨论】:

    标签: c++ template-argument-deduction


    【解决方案1】:

    问题是Arg... 的推导规则存在冲突,一方面std::function&lt;...&gt; 定义它,另一方面输入参数定义它。

    您应该只使用std::invoke 而不是call_fn 或使用typename... Args2 作为输入参数。或者您可以放弃输入要求为 std::function 并接受任何可调用的。

    另一种选择是确保输入参数不用于参数类型推导,但我不确定如何使用可变参数模板 (typename...) - 对语法有一些疑问。

    编辑:让我写一个例子

         template<typename T> foo(std::vector<T> x, T y);
    
         std::vector<double> x;
         int y;
         foo(x,y);
    

    这里foo会因为冲突无法推导出T。解决它的一种方法是确保y 不用于类型推导。

        template<typename T> 
        struct same_type_impl { type=T;};
    
        template<typename T>
         using same_type = typename same_type_impl<T>::type;
    
        template<typename T> foo(std::vector<T> x, same_type<T> y);
    
         std::vector<double> x;
         int y;
         foo(x,y);
    

    这里fooT 推导出为double

    【讨论】:

    • 这几乎可以工作,但same_type 似乎有时会干扰完美转发(我不太清楚为什么有时它会起作用,而不是其他人)。对于某些用途,我收到类似no known conversion from 'int' to 'same_type&lt;int&gt; &amp;&amp;' (aka 'int &amp;&amp;') 的错误。我会玩它,看看我能不能得到一个最小的例子......
    • @CmdrMoozy same_type&lt;int&gt;&amp;&amp; 不是“完美转发的int”,而是对int 的右值引用。由于函数决定了类型,除非您手动更改,否则您不能突然将它们更改为其他类型。如果您希望输入具有灵活性,那么您应该接受 typename... Args2 的输入,并最好对其应用一些 SFINEA 要求,以确保它与 typename... Args 没有太大区别。
    【解决方案2】:

    根据我的测试,我认为std::function 是这里的罪魁祸首。不知何故,它对可变参数模板的类型过于严格(我假设)。

    以下作品:

    #include <iostream>
    #include <string>
    #include <utility>
    
    namespace {
    
    template <typename FnT, typename... Arg>
    auto call_fn(FnT fn, Arg&&... arg) {
        return fn(std::forward<Arg>(arg)...);
    }
    
    std::string test_fn(int, std::string*) { return "hello, world!"; }
    
    }  // namespace
    
    int main(int, char**) {
        std::cout << call_fn(test_fn, 0, nullptr) << "\n";
    
        return EXIT_SUCCESS;
    }
    

    像这样,我们只是模板化了 std::function,现在唯一的要求是我们提供的函数接受这些参数并且是可调用的 (operator())。

    【讨论】:

      猜你喜欢
      • 2016-12-16
      • 1970-01-01
      • 2021-06-22
      • 2016-12-30
      • 1970-01-01
      • 2018-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多