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

时间:2013-02-13 21:24:07

标签: 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)
           )
         );
}

使用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;。

2 个答案:

答案 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