2013-02-13 82 views
16

我需要写一个constexpr函数,但是我觉得不可能。有谁知道这是否可能?可以将addressof()作为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,尽管很多函数形成标头<实用程序>最近已升级为constexpr。

一种可能,虽然不是很有说服力的或有用的用例为这将是:

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

试想一下,MyType的已超负荷运营商&。

+0

我可以问你为什么需要它? – 2013-02-13 21:35:40

+4

对象的地址不是已知的编译时间。 – Erik 2013-02-13 21:38:50

+0

这是不可能的:足够奇怪的投射不能是'constexpr'。但我无法想象需要“constexpr地址”的情况。 – 2013-02-13 21:39:01

回答

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

然后,您可以使用这些辅助功能来选择使用其中的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应用程序的实施案例。海湾合作委员会4.8和更高的工作,铛。

我使用了一个单一实现addressof转发给我的答案的早期版本中的帮助函数,但我最近意识到这不是一个好主意,因为如果addressof<X>是很容易导致ODR违规用于多个翻译单元中的一些类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; 
+0

SFINAE有缺陷。它不捕获非成员或私人成员超载。看到我的评论。 – Potatoswatter 2013-02-15 01:10:14

+0

如果超载无法访问,则内建'&'运算符不可用。 – Potatoswatter 2013-02-15 09:52:36

+0

@Potatoswatter更新后,这对你更好看吗? – hvd 2013-02-15 16:56:56

相关问题