指针与 nullptr_t 的模板参数类型推导

Template parameter type deduction for pointer vs. nullptr_t

考虑以下 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 的定义,使类型推导对指针参数更有效?

问题是 Arg... 的推导规则冲突,一方面 std::function<...> 定义了它,另一方面输入参数定义了它。

您应该只使用 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

根据我的测试,我认为 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())。