用户定义的转换不适用于可变参数函数参数?为什么不呢?

User-defined conversions aren't applied to variadic function arguments? Why not?

本文关键字:参数 函数 为什么不 变参 定义 转换 不适用 适用于 用户      更新时间:2023-10-16

我有一个类,它主要用一些次要的附加功能/元数据包装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)

为什么用例 3 不编译

由于模板参数推导中不考虑隐式转换:

类型

推断不考虑隐式转换(上面列出的类型调整除外(:这是重载解决的工作,稍后会发生。

MyClassstd::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 将失败(,因此这可能是一个坏主意。

顺便说一句:自 C++20 以来std::type_identity受到支持,即使很容易实现。

这就是规则的制定方式。其他比我更有知识的人可能会来给你这里的确切规则(考虑模板替换和转换(,但归根结底就是这样,你无法改变它。

为了考虑转换,您的类需要从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());

您可以将转换运算符保留在它起作用的那些情况下,但请考虑它是否不仅会增加混乱。