addressof()可以实现为constexpr函数吗?

And*_*zej 16 c++ c++11

我需要写一个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)


Pot*_*ter 1

一种部分解决方法是在包装器中定义任何此类对象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)