我有一个主要包装std::variant
的类,其中包含一些次要的附加功能/元数据。
为简化使用,我想提供此包装器类到底层变量类型的用户定义转换,因此可以直接在其上调用诸如std::holds_alternative
之类的函数。
我发现的内容使我非常困惑是否以及何时应用用户定义的转换。这是简化的代码。
#include <iostream>
#include <variant>
struct MyClass
{
// "MyClass" is implicitly convertible to a variant<bool,int>
operator std::variant <bool, int> ()
{
return std::variant<bool,int>(5);
}
};
void thisFunctionTakesOneSpecificVariantType (std::variant<bool,int> v)
{
std::cout << v.index();
}
template <class... types>
void thisFunctionTakesAnyVariantType (std::variant<types...> v)
{
std::cout << v.index();
}
int main ()
{
MyClass mc;
// 1. This compiles and runs as expected,
// proving the user-defined conversion (MyClass -> variant<int,bool>) exists and works "sometimes"
thisFunctionTakesOneSpecificVariantType (mc);
// 2. This compiles and runs as expected,
// proving "thisFunctionTakesAnyVariantType" is well defined
thisFunctionTakesAnyVariantType (std::variant <bool, int> (5));
// 3. But, combining 1 & 2, this fails to compile:
/* fails */ thisFunctionTakesAnyVariantType (mc); // error: no matching function for call to 'thisFunctionTakesAnyVariantType'
// 4. This is what i really want to do, and it also fails to compile
/* fails */ std::holds_alternative<int>(mc); // error: no matching function for call to 'holds_alternative'
// 5. An explicit conversion works for 3 and 4, but why not an implicit conversion?
// After all, the implicit conversion worked in #1
thisFunctionTakesAnyVariantType ( static_cast<std::variant <bool, int>> (mc) );
return EXIT_SUCCESS;
}
为什么不使用情况3和4进行编译,而使用1、2和5进行编译?
在错误消息中,它提供了以下说明:
note: candidate template ignored: could not match 'variant<type-parameter-0-1...>' against 'MyClass'
inline constexpr bool holds_alternative(const variant<_Types...>& __v)
答案 0 :(得分:2)
这就是规则的布局方式。有更多知识的人然后我可能会来这里为您提供确切的规则(考虑了模板替换和转换),但最终还是如此,您无法更改。
要考虑转换,您的课程需要从std::variant<int, bool>
继承。您所有的示例都将编译。但是,我不愿意推荐这种方法,因为实际上组合似乎是正确的设计。
我要做的是提供一种转换方法。您失去了它的隐含方面,但是也许这不是一个坏主意,至少要考虑其他选择。
struct MyClass
{
std::variant<bool, int> to_var() const
{
return {5};
}
}
thisFunctionTakesAnyVariantType (mc.to_var());
std::holds_alternative<int>(mc.to_var());
您可以将转换运算符留给它可以工作的情况,但要考虑它是否不仅会增加混乱。
答案 1 :(得分:2)
为什么不使用案例3进行编译
因为template argument deduction中未考虑隐式转换:
类型推导不考虑隐式转换(上面列出的类型调整除外):这是overload resolution的工作,以后会发生。
将不会考虑从MyClass
到std::variant <bool, int>
的转换,然后类型推导失败。如#5所示,您可以在传递给thisFunctionTakesAnyVariantType
之前应用显式转换。
为什么不使用案例4进行编译
与#3相同的原因。请注意,即使您为参数包指定了一些模板参数,模板参数推导仍会尝试从函数参数中推导以下模板参数。您可以将推论中的函数参数排除为
template <class... types>
void thisFunctionTakesAnyVariantType (std::type_identity_t<std::variant<types...>> v)
然后您可以将其称为
thisFunctionTakesAnyVariantType<bool, int>(mc);
但是请注意,这将使所有模板参数推导无效(并且#2和5将失败),因此这可能不是一个好主意。
BTW:std::type_identity
受支持,因为C ++ 20甚至很容易实现。