我需要写一个constexpr地址函数,但我发现它是不可能的。有谁知道这是否可行?
cppreference.com处的参考实施:
template< class T >
T* addressof(T& arg)
{
return reinterpret_cast<T*>(
&const_cast<char&>(
reinterpret_cast<const volatile char&>(arg)
)
);
}
使用reinterpret_cast(类似于GCC实现),所以它不会这样做。我可以看到最新的C ++标准草案N3485也不要求addressof()是constexpr,即使很多函数形成头文件&lt; utility&gt;最近已升级到constexpr。
可能的,但不是很有说服力或有用的用例是:
constexpr MyType m;
constexpr MyType const* p = &m; // this works today
constexpr MyType const* p = addressof(m); // this is my question
想象一下,MyType重载了operator&amp;。
答案 0 :(得分:8)
如评论中所述,您可以使用SFINAE检测是否可以使用过载的operator&
。正如Potatoswatter在评论中指出的那样,这些需要三次单独检查:
1)是否接受x.operator&()
2)是否接受operator&(x)
前两个是可以定义用户提供的operator&
的两种方式。
3)是否接受&x
第三次检查是必要的,因为x.operator&()
可能会被拒绝,因为operator&
确实存在,但它是私有的。在这种情况下,&x
无效。
可以通过选中sizeof(f(std::declval<T>()))
来实现这些检查,其中f
的重载方式使得返回类型取决于T
是否通过了检查。
namespace addressof_helper {
template <typename T>
static char (&checkaddressof(...))[1];
template <typename T>
static char (&checkaddressof(T &&, typename std::remove_reference<decltype(&std::declval<T &>())>::type * = 0))[2];
template <typename T>
static char (&checknonmember(...))[1];
template <typename T>
static char (&checknonmember(T &&, typename std::remove_reference<decltype(operator&(std::declval<T &>()))>::type * = 0))[2];
template <typename T>
static char (&checkmember(...))[1];
template <typename T>
static char (&checkmember(T &&, typename std::remove_reference<decltype(std::declval<T &>().operator&())>::type * = 0))[2];
}
然后,您可以使用这些帮助函数来选择要使用的addressof
实现:
template <typename T>
constexpr typename std::enable_if<
sizeof(addressof_helper::checkaddressof<T>(std::declval<T>())) == 2
&& sizeof(addressof_helper::checknonmember<T>(std::declval<T>())) == 1
&& sizeof(addressof_helper::checkmember<T>(std::declval<T>())) == 1,
T *>::type addressof(T &t) {
return &t;
}
template <typename T>
/* no constexpr */ typename std::enable_if<
sizeof(addressof_helper::checkaddressof<T>(std::declval<T>())) == 1
|| sizeof(addressof_helper::checknonmember<T>(std::declval<T>())) == 2
|| sizeof(addressof_helper::checkmember<T>(std::declval<T>())) == 2,
T *>::type addressof(T &t) {
return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(t)));
}
只要addressof
没有重载,这就允许在{1}}中使用operator&
。如果它被重载,似乎无法以可在常量表达式中使用的形式可靠地获取地址。
请注意,GCC 4.7拒绝使用此addressof
实施案例。 GCC 4.8及更高版本的工作,以及clang。
我使用addressof
的单个实现转发给我的答案的早期版本中的帮助函数,但我最近意识到这不是一个好主意,因为它很容易导致ODR违规如果addressof<X>
用于多个翻译单元中的某个课程X
,其中一些X
已定义,其中一些X
不完整。有两个独立的功能可以避免这个问题。
唯一剩下的问题是,如果在addressof<X>
的自定义X
定义之前在翻译单元中使用operator&
,则可能会失败。这应该是非常罕见的,这在实践中不是问题。
明智例子的测试用例:
class A { } a;
class B { private: B *operator&(); } b;
class C { C *operator&(); } c;
class D { } d;
D *operator&(D &);
extern class E e;
int main() {
constexpr A *pa = addressof(a);
/* no constexpr */ B *pb = addressof(b);
/* no constexpr */ C *pc = addressof(c);
/* no constexpr */ D *pd = addressof(d);
constexpr E *pe = addressof(e);
}
class E { } e;
答案 1 :(得分:1)
一个部分解决方法是在union
包装器中定义任何此类对象,并将指针传递给union
。指向包装器的指针可以很容易地转换为对类型的引用。指针算法应该适用于包装器数组。但我仍然没有找到一种方法来获取指向具有重载operator&
的类型的指针。
union
只需要一名成员; struct
可以在实践中发挥作用,但理论上,实现可以在开头添加填充。如果你无法在常量表达式中获得MyType *
,那么填充确实没有什么区别。
template< typename t >
union storage_wrapper
{ t obj; };
constexpr storage_wrapper< MyType > m{{ args_to_MyType_constructor }};
constexpr storage_wrapper< MyType > *p = &m; // not overloaded