什么时候 return 类型不完全是 return 类型? (模板专业化,"auto")

When is a return type not exactly a return type? (template specialization, "auto")

TL;DR - 为什么我的一个方法似乎不是 return 它所说的类型,从而导致我的专用模板方法出现问题?

我不是 100% 确定我在处理什么,所以我不确定如何正确地给问题命名。我有两个 classes... Union32 是一个 class,它提供对 multi-byte/multi-word 标量部分的字节序正确的访问(这里的 class出于示例目的而被砍掉)。 class 包含几个模板方法,Get()Set(),并且提供了每个方法的特化。

Modbus32 是存储在 Modbus 寄存器文件中的 32 位标量的模板 class; Modbus 寄存器是 16 位的,因此连接两个允许 storing/accessing 32 位值。但是由于 Modbus 是一种大端协议(16 位寄存器首先传输 MSB),您几乎肯定希望首先存储最高有效位; Modbus32 封装了该行为并使用 Union32 根据机器的字节顺序重新 assemble 32 位值。

Modbus32 也有 Get()Set() 方法,以及 GetUnion() 方法,其中 return 是一个 Union32 对象 assembled 来自 uint16_t mswuint16_t lsw class 成员。 Modbus32 模板的参数是一个 32 位的类型名(例如 uint32_tint32_t),这个类型名(ValueType)用于管理哪个特定的 Union32::Get() 方法在 returning 32 位 Modbus 寄存器的值时被调用。

无论如何,对于示例,以及我在尝试创建 Modbus32::Get()link to example hosted on Coliru,使用 C++14)时遇到的错误:

#include <iostream>
#include <string>
#include <type_traits>
#include <cstdint>

#define INDUCE_COMPILER_ERRORS   0   // Set to 1 to see the compiler errors.

/* Container for 32-bit data, allows endian-correct access to bytes/words. */
class Union32 {
 public:
  Union32() { value = 0; }
  explicit Union32(uint16_t msw, uint16_t lsw) {
    endian_word.lsw = lsw;
    endian_word.msw = msw;
  }
  static constexpr size_t kLength = 4;
  template<typename ValueType> ValueType Get(void) const;
  template<typename ValueType> void Set(ValueType set_value);
  /* Machine is little endian. */
  struct endian_word_t {
    uint16_t lsw;           // Least-significant word.
    uint16_t msw;           // Most-significant word.
  };
  union {
    uint32_t value;             // Integer value.
    int32_t signed_value;       // Signed integer value.
    endian_word_t endian_word;  // Least/most significant 16-bit words.
  };
};

/* Specializations for Union32::Get(). */
template<> inline int32_t Union32::Get<int32_t>(void) const { return signed_value; }
template<> inline uint32_t Union32::Get<uint32_t>(void) const { return value; }
template<> inline void Union32::Set<int32_t>(int32_t set_value) { signed_value = set_value; }
template<> inline void Union32::Set<uint32_t>(uint32_t set_value) { value = set_value; }

/* Class that explicitly stores MSW first in memory, a la Modbus. */
template<typename ValueType,
    class = typename std::enable_if<std::is_arithmetic<ValueType>::value>::type,
    class = typename std::enable_if<sizeof(ValueType) == sizeof(uint32_t)>::type>
struct Modbus32 {
  uint16_t msw;       // Most significant word.
  uint16_t lsw;       // Least significant word.
  inline Union32 GetUnion(void) const {
    return Union32(msw, lsw);  // Creates a Union32 object from the MSW and LSW.
  }
  inline ValueType Get_Working1(void) const {
    Union32 u32 = GetUnion();
    return u32.Get<ValueType>();
  }
  inline ValueType Get_Broken1(void) const {
    /* Error: no matching function for call to 'Union32::Get()' */
    /* Only generates an error when called by conversion operator. */
    return GetUnion().Get();
  }
#if INDUCE_COMPILER_ERRORS   // Set to 1 to see the compiler errors.
  inline ValueType Get_Broken2(void) const {
    /* Error: expected primary-expression before '>' token */
    return GetUnion().Get<ValueType>(); // doesn't like the explicit template parameter, I guess?
  }
  inline ValueType Get_Broken3(void) const {
    auto u32 = GetUnion();
    /* Error: expected primary-expression before '>' token */
    return u32.Get<ValueType>(); // only difference from Get_Working1() is "Union32" vs. "auto"
  }
#endif
  inline operator ValueType() const {
    /* Call one of the above Get() methods here. */
    return Get_Working1();
    //return Get_Broken1();  // will cause compiler error when invoked
  }
};

typedef Modbus32<uint32_t> ModbusUint32;
typedef Modbus32<int32_t> ModbusInt32;

int main()
{
    std::cout << "Hello from GCC " __VERSION__ "!" << std::endl;
    ModbusUint32 foo32;
    foo32.msw = 2;             // Returns 2*65536 + 1*1 = 131073
    foo32.lsw = 1;
    uint32_t value = foo32;    // Calls the implicit conversion operator.
    std::cout << "value = " << value << std::endl;
    return 0;
}

我找到了一种编译器喜欢的 Get() 方法,即 Get_Working1()。此方法声明一个本地 Union32 u32 并将 GetUnion() 方法的结果赋给它,然后在下一个语句中它 return 赋值 u32.Get<ValueType>()。这看起来很简单。

但是,我发现了三种不同的方法来打破这个工作示例,使用方法 Get_Broken1/2/3()Get_Broken1() 可能是转移注意力,因为我预计模板参数推导在这里不起作用,所以现在可能会忽略它。但是其他两个对我来说没有意义,我怀疑它归结为 GetUnion().

的 return 类型(或者可能是 rvalue/lvalue-ness)

Get_Broken2() 采用工作方法并删除 u32 局部变量(左值),而是使用 GetUnion() 作为右值。 Get_Broken3() 变化更少,仅在声明 u32 时用 auto 代替 Union32。然而这两种方法都破坏了编译器(期望主表达式等),尽管看起来在功能上等同于 Get_Working1().

GetUnion() 的 return 类型是 Union32。那么,为什么我不能不加抗议地打电话给 GetUnion().Get<ValueType>() 呢?我认为这可能是 lvalue/rvalue 的事情,但我认为第三个例子反驳了这一点,并告诉我还有其他事情正在发生。在 Get_Broken3() 中,auto u32 = GetUnion(); not 如何导致 u32 成为 Union32 类型?为什么 u32 被显式声明为 Union32 而不是自动声明? u32 怎么可能不是 Union32

总而言之,我在 Get_Working1() 中有工作代码,虽然我已经设法用三个不同的示例破坏了编译器,但我认为 Get_Broken3() 可能暴露了问题的真正根源。不幸的是,我不知道那是什么。如果有人能阐明这一点,那就太好了,谢谢。

gcc和clang都认为GetUnion的return值依赖于Modbus的类型参数。我不知道为什么。

要修复编译错误,请在 Get 之前添加 template

因为 GetUnion().Get 被解析为好像 Get 是从 GetUnion() 编辑的结构 return 中的一个值。

在工作示例 Union32 x=GetUnion(); 中,x 的类型不被视为依赖于模板参数。是 Union32.

我不确定为什么编译器认为具有固定 return 类型的方法的 return 类型依赖于 class 定义主体中的模板参数。它可能只是稍微搞砸了标准 C++ 规则。

所以,尝试:

return GetUnion().template Get<ValueType>();