指针与 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);
此处foo
将T
推导为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()
)。
考虑以下 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);
此处foo
将T
推导为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()
)。