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

Getting constexpr functions resolved without creating parameter objects

短版:

如果我有这样的功能:

constexpr bool has_some_property(Foo) { return true; }

有没有什么方法可以调用函数而不必实际实例化 Foo?如果 Foo 不是默认构造的?

长篇大论:

Anthony Williams 最近 wrote an article 详细介绍了为任何 enum class 专门化特定模板的对象启用的一组免费功能。它遵循 <ios>std::is_error_code 中的类似方案,其中专门针对用户定义的类型或值的模板以允许 enable_if 启用某些功能。就安东尼而言:

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 函数,它可以在与 class:

相同的名称空间中解析
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){

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

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 但在这种情况下不起作用。

有人知道解决这个难题的办法吗?

只是不要使用 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 需要常量表达式的问题。

让我来回答您关于 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,所有代码将以相同的方式工作。

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

您可以使用函数模板,也可以使用 class 模板。已选择以下 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
}