使用可变参数模板助手多次 std::variant 访问
Multiple std::variant visit with variadic templated helper
我正在尝试制作一个有助于处理 N std::variant
类型的函数。
注意:我正在尝试验证所有路径的编译时间。所以 std::optional
和 std::holds_alternative
对我来说不可行。
实现如下:
template<typename T>
using Possible = std::variant<std::monostate, T>;
template<typename... Types>
void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
{
std::visit(
[&](auto&&... args) {
if constexpr ((... &&
std::is_same_v<std::decay_t<decltype(args)>, Types>))
{
return all(std::forward<Types>(args)...);
}
else
{
std::cout << "At least one type is monostate" << std::endl;
}
},
possibles...);
}
使用该函数的示例是:
int main()
{
Possible<int> a = 16;
Possible<bool> b = true;
ifAll([](const int& x, const bool& y)
-> void { std::cout << "All types set!" << std::endl; },
a,
b);
}
但是我得到一个编译器错误:
TestFile.cc: error: no matching function for call to 'ifAll'
ifAll([](const int& x, const bool& y)
^~~~~
TestFile.cc: note: candidate template ignored: could not match
'function<void (type-parameter-0-0...)>' against '(lambda at
TestFile.cc)'
void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
^
为什么我提供的 lambda 与函数签名不匹配?
已尝试修复 1
我试过在 a
和 b
中移动,但仍然不起作用:
ifAll([](const int& x, const bool& y)
-> void { std::cout << "All types set!" << std::endl; },
std::move(a),
std::move(b));
一个简单的解决方案是使用函数对象 + std::optional
:
#include <functional>
#include <optional>
struct Error {};
template <typename F, typename... Args>
decltype(auto) if_all(F&& f, Args&&... args)
{
if ((args && ...)) {
return std::invoke(std::forward<F>(f), *std::forward<Args>(args)...);
} else {
throw Error{};
}
}
用法示例:
#include <functional>
#include <iostream>
int main()
{
std::optional<int> a{5};
std::optional<int> b{10};
std::cout << if_all(std::plus{}, a, b) << '\n';
}
如果你坚持使用std::variant
而不是std::optional
(这可能是因为对它们中的任何一个有一些误解),这个想法是一样的——你需要检查所有参数是否是"empty" 首先(可能使用 std::holds_alternative
),然后展开参数。
以下调用有效:
int main() {
Possible<int> a = 16;
Possible<bool> b = true;
std::function<void(int, bool)> fun = [](int x, bool y) -> void {
std::cout << "All types set!" << std::endl;
};
ifAll(fun,
std::move(a),
std::move(b));
}
或将您的函数签名切换为:
template <typename... Types>
void ifAll(std::function<void(Types...)> const& all, Possible<Types>&... possibles)
然后你可以在没有 std::move
的情况下调用它:
int main() {
Possible<int> a = 16;
Possible<bool> b = true;
std::function<void(int, bool)> fun = [](int x, bool y) -> void {
std::cout << "All types set!" << std::endl;
};
ifAll(fun, a, b);
}
抱歉。我认为问题是双重的:
- 您不使用 std::move 将左值(变量
a
、b
)传递给采用 xvalue 参数的函数(Possible<Types>&&...
扩展。)
- 您在将 lambda 传递给您的函数时尝试将
std::function<void(const int&, const int&)>
转换为 std::function<void(int, int)>
。
给定其他参数,编译器将类型与 (int, int) 匹配。然后它会尝试寻找 std::function<void(int, int)>
作为第一个参数。相反,它获得类型为 void(*)(const int&, const int&)
的函数 lambda。因此签名不匹配。
我的建议是从标准库中获取提示,而不是尝试使用特定类型的 std::function 对象,而是为函数类型添加模板参数 FuncType
,并且使用它传入函数指针。我认为这可能就是标准算法将函数类型作为模板参数的原因,即使它们可以推断出应该从其他模板参数传递的近似函数签名。
正如已接受的答案一样,一个问题是您不应该在 Possible<Types>&&
中使用 &&
。这意味着您只能接受右值参数,但在您的用法示例中,a
和 b
是左值。最简单的处理方法是使用转发引用来处理所有个案例。另一个问题是您允许从 std::function
参数推导出 Types...
。 lambda 的参数可能与 Possible
不完全匹配,因此您应该防止从 std::function
推导出 Types...
并且只从 Possible
中获取它们.最简单的方法是在参数类型的模板参数中加入某种类型的计算,这使得编译器放弃尝试对该参数进行推导。有时,你只是使用一个虚拟的 type_identity_t
函数,但是,在这里,我们可以做一些更有趣的事情,一石二鸟
template<typename T>
struct get_handler_argument;
template<typename T>
struct get_handler_argument<Possible<T>> { using type = T&; };
template<typename T>
struct get_handler_argument<Possible<T> const> { using type = T const&; };
template<typename T>
using get_handler_argument_t = typename get_handler_argument<std::remove_reference_t<T>>::type;
template<typename... Vars>
void ifAll(std::function<void(get_handler_argument_t<Vars>...)> all, Vars&&... possibles) {
std::visit(
[&](auto&&... args) {
if constexpr ((... &&
std::is_same_v<std::decay_t<decltype(args)>, std::decay_t<get_handler_argument_t<Vars>>>)) {
return all(std::forward<get_handler_argument_t<Vars>>(args)...);
} else {
std::cout << "At least one type is monostate" << std::endl;
}
},
possibles...);
}
您在这里失去的是,您不能再直接提供 "present" 值来代替 Possible
值,而无需在 Possible
构造函数中显式包装它。这没有损失;只是捕获它而不是传递它。您获得的是,您可以使用函数调用或您拥有的东西来初始化 Possible
,而不必先手动将它们保存在变量中。与当前接受的答案不同,您的示例按原样使用此定义。
Possible<Types>&&
实际上是右值引用,而不是转发引用。
您必须添加重载来处理不同的情况。
template<F, typename... Types> void ifAll(F, const Possible<Types>&...);
template<F, typename... Types> void ifAll(F, Possible<Types>&...);
template<F, typename... Types> void ifAll(F, Possible<Types>&&...);
在template<typename... Types>
void ifAll(std::function<void(Types...)>, Possible<Types>&...)
中,Types
需要推导两次,推导不匹配就会出错
在你的情况下,你有第一个 const int&, const bool&
(自 CTAD 与 C++17)
然后 int, bool
。不匹配,所以报错。
解决问题的几种方法:
修复调用站点(脆弱的解决方案):
std::function<int, bool> f = [](const int& x, const bool& y)
-> void { std::cout << "All types set!" << std::endl; };
ifAll(fun, a, b); // Assuming overload with lvalue references
使一个参数不可推导:
template<typename... Types>
void ifAll(std::function<void(std::identity_type_t<Types>...)>, Possible<Types>&...)
添加额外的模板参数:
template<typename... Args, typename... Types>
void ifAll(std::function<void(Args...)>, Possible<Types>&...)
可能与一些 SFINAE。
完全改变论点(我会选择一个):
template<F, typename... Types>
void ifAll(F, Possible<Types>&...)
// or even
template<F, typename... Ts>
void ifAll(F, Ts&&...); // Forwarding reference, no extra overloads to add.
可能与一些 SFINAE。
我正在尝试制作一个有助于处理 N std::variant
类型的函数。
注意:我正在尝试验证所有路径的编译时间。所以 std::optional
和 std::holds_alternative
对我来说不可行。
实现如下:
template<typename T>
using Possible = std::variant<std::monostate, T>;
template<typename... Types>
void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
{
std::visit(
[&](auto&&... args) {
if constexpr ((... &&
std::is_same_v<std::decay_t<decltype(args)>, Types>))
{
return all(std::forward<Types>(args)...);
}
else
{
std::cout << "At least one type is monostate" << std::endl;
}
},
possibles...);
}
使用该函数的示例是:
int main()
{
Possible<int> a = 16;
Possible<bool> b = true;
ifAll([](const int& x, const bool& y)
-> void { std::cout << "All types set!" << std::endl; },
a,
b);
}
但是我得到一个编译器错误:
TestFile.cc: error: no matching function for call to 'ifAll'
ifAll([](const int& x, const bool& y)
^~~~~
TestFile.cc: note: candidate template ignored: could not match
'function<void (type-parameter-0-0...)>' against '(lambda at
TestFile.cc)'
void ifAll(std::function<void(Types...)> all, Possible<Types>&&... possibles)
^
为什么我提供的 lambda 与函数签名不匹配?
已尝试修复 1
我试过在 a
和 b
中移动,但仍然不起作用:
ifAll([](const int& x, const bool& y)
-> void { std::cout << "All types set!" << std::endl; },
std::move(a),
std::move(b));
一个简单的解决方案是使用函数对象 + std::optional
:
#include <functional>
#include <optional>
struct Error {};
template <typename F, typename... Args>
decltype(auto) if_all(F&& f, Args&&... args)
{
if ((args && ...)) {
return std::invoke(std::forward<F>(f), *std::forward<Args>(args)...);
} else {
throw Error{};
}
}
用法示例:
#include <functional>
#include <iostream>
int main()
{
std::optional<int> a{5};
std::optional<int> b{10};
std::cout << if_all(std::plus{}, a, b) << '\n';
}
如果你坚持使用std::variant
而不是std::optional
(这可能是因为对它们中的任何一个有一些误解),这个想法是一样的——你需要检查所有参数是否是"empty" 首先(可能使用 std::holds_alternative
),然后展开参数。
以下调用有效:
int main() {
Possible<int> a = 16;
Possible<bool> b = true;
std::function<void(int, bool)> fun = [](int x, bool y) -> void {
std::cout << "All types set!" << std::endl;
};
ifAll(fun,
std::move(a),
std::move(b));
}
或将您的函数签名切换为:
template <typename... Types>
void ifAll(std::function<void(Types...)> const& all, Possible<Types>&... possibles)
然后你可以在没有 std::move
的情况下调用它:
int main() {
Possible<int> a = 16;
Possible<bool> b = true;
std::function<void(int, bool)> fun = [](int x, bool y) -> void {
std::cout << "All types set!" << std::endl;
};
ifAll(fun, a, b);
}
抱歉。我认为问题是双重的:
- 您不使用 std::move 将左值(变量
a
、b
)传递给采用 xvalue 参数的函数(Possible<Types>&&...
扩展。) - 您在将 lambda 传递给您的函数时尝试将
std::function<void(const int&, const int&)>
转换为std::function<void(int, int)>
。
给定其他参数,编译器将类型与 (int, int) 匹配。然后它会尝试寻找 std::function<void(int, int)>
作为第一个参数。相反,它获得类型为 void(*)(const int&, const int&)
的函数 lambda。因此签名不匹配。
我的建议是从标准库中获取提示,而不是尝试使用特定类型的 std::function 对象,而是为函数类型添加模板参数 FuncType
,并且使用它传入函数指针。我认为这可能就是标准算法将函数类型作为模板参数的原因,即使它们可以推断出应该从其他模板参数传递的近似函数签名。
正如已接受的答案一样,一个问题是您不应该在 Possible<Types>&&
中使用 &&
。这意味着您只能接受右值参数,但在您的用法示例中,a
和 b
是左值。最简单的处理方法是使用转发引用来处理所有个案例。另一个问题是您允许从 std::function
参数推导出 Types...
。 lambda 的参数可能与 Possible
不完全匹配,因此您应该防止从 std::function
推导出 Types...
并且只从 Possible
中获取它们.最简单的方法是在参数类型的模板参数中加入某种类型的计算,这使得编译器放弃尝试对该参数进行推导。有时,你只是使用一个虚拟的 type_identity_t
函数,但是,在这里,我们可以做一些更有趣的事情,一石二鸟
template<typename T>
struct get_handler_argument;
template<typename T>
struct get_handler_argument<Possible<T>> { using type = T&; };
template<typename T>
struct get_handler_argument<Possible<T> const> { using type = T const&; };
template<typename T>
using get_handler_argument_t = typename get_handler_argument<std::remove_reference_t<T>>::type;
template<typename... Vars>
void ifAll(std::function<void(get_handler_argument_t<Vars>...)> all, Vars&&... possibles) {
std::visit(
[&](auto&&... args) {
if constexpr ((... &&
std::is_same_v<std::decay_t<decltype(args)>, std::decay_t<get_handler_argument_t<Vars>>>)) {
return all(std::forward<get_handler_argument_t<Vars>>(args)...);
} else {
std::cout << "At least one type is monostate" << std::endl;
}
},
possibles...);
}
您在这里失去的是,您不能再直接提供 "present" 值来代替 Possible
值,而无需在 Possible
构造函数中显式包装它。这没有损失;只是捕获它而不是传递它。您获得的是,您可以使用函数调用或您拥有的东西来初始化 Possible
,而不必先手动将它们保存在变量中。与当前接受的答案不同,您的示例按原样使用此定义。
Possible<Types>&&
实际上是右值引用,而不是转发引用。
您必须添加重载来处理不同的情况。
template<F, typename... Types> void ifAll(F, const Possible<Types>&...);
template<F, typename... Types> void ifAll(F, Possible<Types>&...);
template<F, typename... Types> void ifAll(F, Possible<Types>&&...);
在template<typename... Types>
void ifAll(std::function<void(Types...)>, Possible<Types>&...)
中,Types
需要推导两次,推导不匹配就会出错
在你的情况下,你有第一个 const int&, const bool&
(自 CTAD 与 C++17)
然后 int, bool
。不匹配,所以报错。
解决问题的几种方法:
修复调用站点(脆弱的解决方案):
std::function<int, bool> f = [](const int& x, const bool& y) -> void { std::cout << "All types set!" << std::endl; }; ifAll(fun, a, b); // Assuming overload with lvalue references
使一个参数不可推导:
template<typename... Types> void ifAll(std::function<void(std::identity_type_t<Types>...)>, Possible<Types>&...)
添加额外的模板参数:
template<typename... Args, typename... Types> void ifAll(std::function<void(Args...)>, Possible<Types>&...)
可能与一些 SFINAE。
完全改变论点(我会选择一个):
template<F, typename... Types> void ifAll(F, Possible<Types>&...) // or even template<F, typename... Ts> void ifAll(F, Ts&&...); // Forwarding reference, no extra overloads to add.
可能与一些 SFINAE。