什么时候 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 msw
和 uint16_t lsw
class 成员。 Modbus32
模板的参数是一个 32 位的类型名(例如 uint32_t
、int32_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>();
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 msw
和 uint16_t lsw
class 成员。 Modbus32
模板的参数是一个 32 位的类型名(例如 uint32_t
、int32_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()
.
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>();