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

时间:2020-08-30 21:20:50

标签: c++ template-argument-deduction

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

2 个答案:

答案 0 :(得分:1)

问题在于,一方面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);

这里foo会推论Tdouble

答案 1 :(得分:0)

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