Определенные пользователем преобразования не применяются к аргументам переменных функций? Почему нет? - PullRequest
4 голосов
/ 15 октября 2019

У меня есть класс, который в основном оборачивает 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)

Ответы [ 2 ]

2 голосов
/ 15 октября 2019

Почему бы не использовать прецеденты 3 скомпилировать

Поскольку неявные преобразования не учитываются при выводе аргумента шаблона :

ТипВывод не учитывает неявные преобразования (кроме приведенных выше корректировок типов): это работа для разрешения перегрузки , которое происходит позже.

Преобразование из 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 потерпит неудачу), так что это может быть плохой идеей.

Кстати: std::type_identity поддерживается начиная с C ++ 20, даже если его легко реализовать.

2 голосов
/ 15 октября 2019

Так изложены правила. Кто-то еще с большим знанием, чем я, может прийти и дать вам точные действующие правила (замена шаблонов и рассмотрение конверсий), но в конце концов это то, что есть, и вы не можете это изменить.

Для того, чтобы преобразование было рассмотрено, ваш класс должен наследовать от 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());

Вы можете оставить оператор преобразования для тех ситуаций, где он работает, но учтитеесли это не добавляет путаницы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...