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

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

我有一个 class,它主要包含一个 std::variant 和一些小的附加 functionality/meta-data。

为了使用简单,我想提供这个包装器 class 到底层变体类型的用户定义转换,这样就可以直接调用像 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)

规则就是这样制定的。其他比我知识更多的人可能会来给你这里的确切规则(考虑模板替换和转换),但最终它就是它,你无法改变它。

为了将转换视为您的 class 需要继承自 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());

您可以将转换运算符保留在它有效的情况下,但请考虑它是否只会增加混乱。

Why don't use cases 3 compile

因为template argument deduction不考虑隐式转换:

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

不考虑从MyClassstd::variant <bool, int>的转换,类型推导失败。正如 #5 所示,您可以在传递给 thisFunctionTakesAnyVariantType.

之前应用显式转换

Why don't use cases 4 compile

与#3 相同的原因。请注意,即使您为参数包模板参数推导指定了一些模板参数,仍然会尝试从函数参数中推导出以下模板参数。您可以使用从推导中排除函数参数 as

template <class... types>
void thisFunctionTakesAnyVariantType (std::type_identity_t<std::variant<types...>> v)

那么你可以称它为

thisFunctionTakesAnyVariantType<bool, int>(mc);

但请注意,这将使所有模板参数推导无效(#2 和 5 会失败),因此这可能不是一个好主意。

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