17

我需要写一个 constexpr addressof 函数,但我觉得不可能。有谁知道这是否可能?

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,即使标题 <utility> 中的许多函数最近已升级为 constexpr。

一个可能的,虽然不是很有说服力或有用的用例是:

constexpr MyType m;
constexpr MyType const* p = &m;           // this works today
constexpr MyType const* p = addressof(m); // this is my question

假设 MyType 重载了 operator&。

4

2 回答 2

9

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];
}

然后,您可以使用这些辅助函数来选择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只要operator&不重载,这允许在常量表达式中使用。如果它被重载,似乎无法以可用于常量表达式的形式可靠地获取地址。

请注意,GCC 4.7 拒绝addressof在它应该工作的地方使用这个实现案例。GCC 4.8 和更高版本可以工作,clang 也是如此。

我在我的答案的早期版本中使用了转发到辅助函数的单个实现addressof,但最近我意识到这不是一个好主意,因为如果addressof<X>将它用于X多个类中的某个类,它很容易导致 ODR 违规翻译单元,有些X是定义的,有些X是不完整的。拥有两个独立的功能可以避免这个问题。

唯一剩下的问题是如果在定义's customaddressof<X>之前在翻译单元中使用它可能会失败。希望这应该足够罕见,以至于在实践中这不是问题。Xoperator&

合理示例的测试用例:

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;
于 2013-02-14T19:10:22.940 回答
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
于 2013-02-14T08:20:03.027 回答