在不创建参数对象的情况下解析constexpr函数

时间:2015-02-10 03:46:14

标签: c++ templates c++11 constexpr argument-dependent-lookup

简短版:

如果我的功能如下:

constexpr bool has_some_property(Foo) { return true; }

有没有办法调用函数而不必实际实例化Foo?假设Foo不是默认可构造的吗?

漫长的版本:

Anthony Williams最近wrote an article详细说明了为专门用于特定模板的任何enum class对象启用的一组免费函数。它遵循<ios>std::is_error_code中的类似方案,其中一个方法专门为用户定义的类型或值提供模板,以允许enable_if启用某些功能。在Anthony的案例中:

template<>
struct enable_bitmask_operators<my_bitmask>{
    static constexpr bool enable=true;
};

然后定义运算符时:

template<typename E>
typename std::enable_if<enable_bitmask_operators<E>::enable,E>::type
operator|(E lhs,E rhs){

这种技术的问题在于模板特化必须与原始模板位于同一名称空间中,因此这不起作用:

namespace mystuff {
    enum class Foo {
        ...
    };

    // Fail: wrong namespace
    template<>
    struct enable_bitmask_operators<Foo> : std::true_type {}

另一种方法是使用constexpr函数,该函数可以在与类相同的命名空间中解析:

namespace mystuff {
    enum class Foo {
        ...
    };
    constexpr bool enable_bitmask_operators(Foo) { return true; }

然后定义:

template<typename E>
typename std::enable_if<enable_bitmask_operators(E()),E>::type
operator|(E lhs,E rhs){

关于这一点的好处是,即使使用嵌套类,它也能很好地工作。它的问题是它需要一个默认的可构造类。这适用于我们的enum class示例,但它不能作为专业化问题的一般解决方案。因此,如果我们想象尝试使用constexpr函数而不是其他类的模板特化,我们可能会遇到其他失败:

struct Foo {
    Foo() = delete;
};
constexpr bool has_some_property(Foo) { return true; }

...

// Fail for Foo...use of deleted function
template<typename E>
typename std::enable_if<has_some_property(E()),E>::type doStuff() {}

它有点令人沮丧,因为我实际上需要要创建的对象,我只是希望ADL能够识别要调用的constexpr函数。我一直在想应该有一些方法我可以说我想要这个功能,而不必实际创建对象。我曾与std::declval玩过,但在这种情况下并不起作用。

有人看到解决这个窘境的方法吗?

3 个答案:

答案 0 :(得分:8)

请勿使用constexpr

std::true_type has_some_property(Foo&& );

Foo有吗?

using has_it = decltype(has_some_property(std::declval<Foo>()));
static_assert(has_it::value, "It should!");

这是一个未评估的上下文,因此我们永远不必调用任何Foo构造函数。并且可以回避constexpr需要常量表达式的问题。

答案 1 :(得分:0)

让我回答一下围绕enable_if技巧的第一个问题:

#include <type_traits>

namespace LibBitmasks {
    template<typename E>
    struct is_bitmask: std::false_type {
    };
}

template<typename E>
typename std::enable_if<LibBitmasks::is_bitmask<E>::value, E>::type operator |(E lhs, E rhs) {
    return static_cast<E>(static_cast<typename std::underlying_type<E>::type>(lhs) |
                          static_cast<typename std::underlying_type<E>::type>(rhs));
}

namespace MyLib {
    enum class Q {
        Q1 = 1,
        Q2 = 2,
        Q3 = 3
    };
}

namespace LibBitmasks {
    template<>
    struct is_bitmask<MyLib::Q>: std::true_type {
    };
}

int main() {
    using MyLib::Q;
    return (Q::Q1 | Q::Q2) == Q::Q3 ? 0 : 42;
}

如果您担心全局命名空间污染(更具体地说,关于与其他operator |重载的潜在冲突),那么您可以将其隐藏在LibBitmasks::ops命名空间中。然后,只要您想使用运算符,只需说using namespace LibBitmasks::ops,所有代码都将以相同的方式工作。

答案 2 :(得分:0)

似乎是一个简单的问题:可以制作不带参数的函数模板。这样,您根本不需要创建任何值即可调用该函数:)

您可以使用功能模板或类模板。选择了以下C ++ 11,C ++ 14和C ++ 17的变体,以便在使用给定C ++标准中可用的功能时提供最短的类型表达式。

#include <type_traits>

struct Foo {
    Foo() = delete;
    Foo(bool) {}
};

// C++11
template <typename T> constexpr bool has_some_property1() { return false; }
template <> constexpr bool has_some_property1<Foo>() { return true; }

// C++11
template <typename T> struct has_some_property2 : std::false_type {};
template <> struct has_some_property2<Foo> : std::true_type {};

// C++17
template <typename T> constexpr bool has_some_property2_v = has_some_property2<T>::value;

template<typename E>  // C++11
typename std::enable_if<has_some_property1<E>(), E>::type doStuff1() { return {true}; }

template <typename E>  // C++14 (enable_if_t)
typename std::enable_if_t<has_some_property1<E>(), E> doStuff2a() { return {true}; } 
template <typename E>
typename std::enable_if_t<has_some_property2<E>::value, E> doStuff2b() { return {true}; } 

template <typename E>  // C++17 (..._v)
typename std::enable_if_t<has_some_property2_v<E>, E> doStuff2c() { return {true}; }

int main()
{
    doStuff1<Foo>();    // compiles
    doStuff2a<Foo>();   // compiles
    doStuff2b<Foo>();   // compiles
    doStuff2c<Foo>();   // compiles
    #if 0
    doStuff1<bool>();   // fails to compile
    doStuff2a<bool>();  // fails to compile
    doStuff2b<bool>();  // fails to compile
    doStuff2c<bool>();  // fails to compile
    #endif
}