【问题标题】:Template deduction vs. implicit user-defined conversion operator模板推导与隐式用户定义转换运算符
【发布时间】:2018-04-04 08:32:29
【问题描述】:

我尝试实现一个涉及模板的用户定义类型转换的小例子。

#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: no match for ‘operator*=’ (operand types are ‘uint32_t {aka unsigned int}’ and ‘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)

因此,打开这些概念后,我假设将 Number 转换为 unsigned integer 类型的唯一方法 - 那么为什么编译器不足以推断转换?

【问题讨论】:

  • Clang 会给您一条错误消息,立即回答您的问题。
  • Clang 不支持概念,所以请澄清您的声明。
  • 这个问题的概念是正交的,可以去掉概念声明,把UIntegral换成typename,结果是一样的。
  • 您的概念定义要求std::is_integral_v&lt;T&gt; &amp;&amp; !std::is_signed_v&lt;T&gt; 是一个有效的表达式;它对该表达式的 value 没有任何要求。你几乎肯定想要template&lt;typename T&gt; concept bool UIntegral = std::is_integral_v&lt;T&gt; &amp;&amp; !std::is_signed_v&lt;T&gt;;
  • 完美,谢谢! :)

标签: c++ templates implicit-conversion c++-concepts c++20


【解决方案1】:

requires 概念表达式的工作方式与 SFINAE 类似,它只检查表达式是否有效,但不计算它。

要将概念实际限制 T无符号整数类型,请使用bool 表达式:

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

这会解决您的问题吗?不幸的是,没有,请继续阅读...

为什么不能自动推导出模板化的转换算子?

编写错误的 C++ 代码是解决编译器错误的可靠方法 :-) gcc 中有超过 1,000 个已确认未解决的错误。

是的,找到了模板化转换运算符should be,而"no match for 'operator*='" 错误消息应该改为"ambiguous overload for 'operator*='"

因此,打开这些概念后,我假设将 Number 转换为无符号整数类型的唯一方法 - 那么为什么编译器不足以推断转换?

即使概念要求和编译器错误被修复,歧义仍然存在,特别是这四个:

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)

这是因为对于每个可能的提升的内置类型,都有a lot 的内置运算符,并且intlonglong 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;
};

【讨论】:

  • 额外说明为什么“解决方案 2”几乎总是足够好:由于隐式转换通常允许内置转换,然后是用户定义的转换,然后是内置转换, Number 仍可用于需要特定不同算术类型的其他情况。
  • 谢谢!这是一个非常体面的解释。
猜你喜欢
  • 2018-01-25
  • 1970-01-01
  • 1970-01-01
  • 2017-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-22
相关资源
最近更新 更多