我需要写一个constexpr地址的函数,但我发现它是不可能的.有谁知道这是否可能?
cppreference.com上的参考实现:
template< class T >
T* addressof(T& arg)
{
return reinterpret_cast<T*>(
&const_cast<char&>(
reinterpret_cast<const volatile char&>(arg)
)
);
}
Run Code Online (Sandbox Code Playgroud)
使用reinterpret_cast(类似于GCC实现),所以它不会这样做.我可以看到最新的C++标准草案,N3485也不要求addressof()是constexpr,即使很多函数形成头<utility>最近已升级到constexpr.
一个可能的,虽然不是很有说服力或有用的用例,它将是:
constexpr MyType m;
constexpr MyType const* p = &m; // this works today
constexpr MyType const* p = addressof(m); // this is my question
Run Code Online (Sandbox Code Playgroud)
想象一下,MyType重载了operator&.
小智 8
如评论中所述,您可以operator&使用SFINAE 检测是否可以使用过载.正如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];
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用这些帮助程序函数来选择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)));
}
Run Code Online (Sandbox Code Playgroud)
addressof只要operator&不重载,这允许在常量表达式中使用.如果它被重载,似乎无法以可在常量表达式中使用的形式可靠地获取地址.
请注意,GCC 4.7拒绝使用addressof它应该工作的实现情况.GCC 4.8及更高的工作,和clang一样.
我addressof在我的答案的早期版本中使用了转发给辅助函数的单个实现,但我最近意识到这不是一个好主意,因为如果在多个addressof<X>类X中使用某些类,它很容易导致ODR违规翻译单位,其中一些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;
Run Code Online (Sandbox Code Playgroud)
一种部分解决方法是在包装器中定义任何此类对象union,并将指针传递到union. 指向包装器的指针可以轻松转换为对该类型的引用。指针算术应该适用于包装数组。但我仍然没有找到一种方法来获取指向重载类型的指针operator&。
只union需要一名成员;struct在实践中可行,但理论上,实现可以在开始时添加填充。MyType *如果您无论如何都无法在常量表达式中获得 a ,那么填充并不会真正产生影响。
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
Run Code Online (Sandbox Code Playgroud)