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

Getting constexpr functions resolved without creating parameter objects

本文关键字:constexpr 函数 情况下 创建 参数 对象      更新时间:2023-10-16

短版本:

如果我有这样的功能:

constexpr bool has_some_property(Foo) { return true; }

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

冗长版本:

Anthony Williams最近写了一篇文章,详细介绍了为任何专门化特定模板的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函数,该函数可以在与类相同的命名空间中解析:

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

有人能解决这个难题吗?

只是不要使用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,所有代码都将以相同的方式工作。

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

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