Выведение шаблона и неявный пользовательский оператор преобразования

Я попытался реализовать небольшой пример преобразования пользовательского типа с использованием шаблонов.

#include <cassert>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <type_traits>

template <typename T>
concept bool UIntegral = requires() {
    std::is_integral_v<T> && !std::is_signed_v<T>;
};

class Number
{
public:
    Number(uint32_t number): _number(number)
    {
        if (number == 1) {
            number = 0;
        }
        
        for (; number > 1; number /= 10);
        if (number == 0) {
            throw std::logic_error("scale must be a factor of 10");
        }
    }
    
    template <UIntegral T>
    operator T() const
    {
        return static_cast<T>(this->_number);
    }
        
private:
    uint32_t _number;
};

void changeScale(uint32_t& magnitude, Number scale)
{
    //magnitude *= scale.operator uint32_t();
    magnitude *= scale;
}

int main()
{
    uint32_t something = 5;
    changeScale(something, 100);
    std::cout << something << std::endl;

    return 0;
}

Я получаю следующую ошибку компиляции (из GCC 7.3.0):

main.cpp: в функции «void changeScale (uint32_t &, Number)»:

main.cpp: 40: 15: error: нет соответствия для «operator * =» (типы операндов: «uint32_t {aka unsigned int}» и «Number»)

величина * = масштаб;

Обратите внимание на закомментированную строку - она ​​работает:

//magnitude *= scale.operator uint32_t();

Почему шаблонный оператор преобразования не может быть выведен автоматически? Заранее благодарим за помощь.

[РЕДАКТИРОВАТЬ]

Я последовал совету удалить концепции, чтобы использовать Clang и увидеть его сообщения об ошибках. Я получил следующее (это усечено, но достаточно):

main.cpp:34:15: error: use of overloaded operator '*=' is ambiguous (with operand types 'uint32_t'
  (aka 'unsigned int') and 'Number')
magnitude *= scale;
~~~~~~~~~ ^  ~~~~~
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, float)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, double)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, long double)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, __float128)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, int)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, long long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, __int128)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned int)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned __int128)

Итак, включив концепции, я предполагаю, что единственный способ привести число к типу беззнаковый интеграл - тогда почему компилятору недостаточно для вывода преобразования?


person Community    schedule 04.04.2018    source источник
comment
Clang выдает сообщение об ошибке, которое сразу же дает ответ на ваш вопрос.   -  person n. 1.8e9-where's-my-share m.    schedule 04.04.2018
comment
Clang не поддерживает концепции, поэтому, пожалуйста, поясните свое утверждение.   -  person    schedule 04.04.2018
comment
Концепции ортогональны этой проблеме, вы можете просто удалить объявление концепции и заменить UIntegral на typename, результат тот же.   -  person n. 1.8e9-where's-my-share m.    schedule 04.04.2018
comment
Определение вашей концепции требует, чтобы std::is_integral_v<T> && !std::is_signed_v<T> было допустимым выражением; он не предъявляет никаких требований к значению этого выражения. Вместо этого вы почти наверняка захотите template<typename T> concept bool UIntegral = std::is_integral_v<T> && !std::is_signed_v<T>;.   -  person Casey    schedule 04.04.2018
comment
Отлично, спасибо! :)   -  person    schedule 06.04.2018


Ответы (1)


Выражение концепции requires работает аналогично SFINAE, оно только проверяет, является ли выражение действительным, но не оценивает его.

Чтобы концепция действительно ограничивала T целочисленным типом без знака, используйте выражение bool:

template<typename T>
concept bool UIntegral = std::is_integral_v<T> && !std::is_signed_v<T>;

Это решит вашу проблему? К сожалению, нет, читайте дальше ...

Почему шаблонный оператор преобразования не может быть выведен автоматически?

Написание ошибочного кода C ++ - верный способ найти ошибку компилятора :-) В gcc более 1000 подтвержденных нерешенных ошибок.

Да, должен быть найден шаблонный оператор преобразования , а вместо "ambiguous overload for 'operator*='" должно появиться сообщение об ошибке "no match for 'operator*='".

Итак, включив концепции, я предполагаю, что единственный способ привести число к беззнаковому целочисленному типу - тогда почему компилятору недостаточно для вывода преобразования?

Даже если концептуальное требование и ошибка компилятора будут исправлены, неоднозначность останется, особенно эти четыре:

main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned int)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned __int128)

Это потому, что существует много встроенных операторов для каждого мыслимого расширенного встроенного типа, а int, long, long long и __int128 являются интегральными типами.

По этой причине обычно не рекомендуется использовать шаблон преобразования во встроенный тип.

Решение 1. Создайте шаблон оператора преобразования explicit и явно запросите преобразование.

    magnitude *= static_cast<uint32_t>(scale);
    // or
    magnitude *= static_cast<decltype(magnitude)>(scale);

Решение 2. Просто выполните преобразование без шаблона в тип _number:

struct Number
{
    using NumberType = uint32_t;
    operator NumberType () const
    {
        return this->_number;
    }
    NumberType _number;
};
person rustyx    schedule 06.04.2018
comment
Дополнительное примечание, почему Решение 2 почти всегда будет достаточно хорошим: поскольку неявное преобразование обычно допускает встроенное преобразование, за которым следует пользовательское преобразование, за которым следует встроенное преобразование, Number все еще можно использовать в других ситуациях, требующих конкретный другой арифметический тип. - person aschepler; 06.04.2018
comment
Спасибо! Это действительно достойное объяснение. - person ; 09.04.2018