【问题标题】:Is it possible for a type to have different definitions depending on whether it's defined in a constexpr context?一个类型是否可以根据它是否在 constexpr 上下文中定义而具有不同的定义?
【发布时间】:2021-11-17 20:51:52
【问题描述】:

我希望有一个在 constexpr 上下文中使用时行为不同的类型。

看这个例子:

#include <cstdio>
#include <type_traits>

template <bool IS_CONSTANT_EVAL>
struct Bar;

template <>
struct Bar<false> {
    constexpr int doIt() const {
        return 0;
    }
};

template <>
struct Bar<true> {
    constexpr int doIt() const {
        return 1;
    }
};

constexpr int foo() {
    if (std::is_constant_evaluated()) {
        Bar<true> v;
        return v.doIt();
    } else {
        Bar<false> v;
        return v.doIt();
    }
}

int main() {
    int rt = foo();
    constexpr int ce = foo();

    printf("Runtime: %d ConstEval: %d \n", rt, ce);
}

在此示例中,foo 无论是从 constexpr 上下文中调用,其行为都会有所不同,因为特化依赖于 constexpr-ness,因此它会打印:

Runtime: 0 ConstEval: 1

但如果我尝试将其简化为:

constexpr int foo() {
    Bar<std::is_constant_evaluated()> v;
    return v.doIt();
}

它不再起作用了,因为std::is_constant_evaluated() 总是返回true 作为模板参数。

甚至,我想要一个更简单的foo,像这样:

constexpr int foo() {
    Bar v;
    return v.doIt();
}

所以Bar 本身应该以某种方式处理 constexpr-ness,我不想在每次使用 Bar 时重复 std::is_constant_evaluated()

可以这样做吗?

注意:我实际上不想有不同的行为,只是不同的实现。由于 constexpr 的限制,我需要根据 constexpr-ness 对 Bar 进行不同的实现。例如,memcpyreinterpret_cast 使表达式不是常量。因此,如果Bar 用于常量表达式,我需要使用一些与 constexpr 兼容的技术来替换memcpyreinterpret_cast

注意 2:我确实需要不同的 Bar 定义,不仅仅是 doIt 需要不同。如果在常量表达式中使用memcpyreinterpret_cast,则替换Bar 需要额外的成员。

【问题讨论】:

  • Bar::doIt() 中的示例if (std::is_constant_evaluated()) 应该可以完成这项工作,不是吗? Demo
  • 为什么不在Bar 实现中检查std::is_constant_evaluated() 是否有需要这种实现分支的函数?
  • 我重写了您的示例,这样您就无需在每次使用 Bar 时为 std::is_constant_evaluated() 烦恼:See here。但这不是不同的实现,只是不同的行为。但是您在问题中的示例不需要不同的实现即可工作。也许如果你能给我们一个你真正想做的真实例子,就会更容易理解你的问题/需求。
  • @Fareanor:因为Bar在常量表达式中使用时需要一些额外的成员,所以不同的不仅仅是函数实现。
  • 难道你不能拥有那些额外的成员,并且只有当 if(std::is_constant_evaluated())true 时才使用它们?如果您不介意额外的空间浪费,以防万一。

标签: c++


【解决方案1】:

这是在黑暗中拍摄的,因为所讨论的实际课程可能不允许这样做。但是 C++20 将常量表达式放宽了一个很大的因素。具体来说,现在允许虚函数调用,您可以执行以下操作:

class Bar {
    struct impl {
        constexpr virtual int  doIt () const = 0;
    };
    
    struct : impl {
        constexpr int  doIt() const override { return 1; }
    } _c;
    
    struct : impl {
        constexpr int  doIt() const override { return 0; }
    } _r;

    impl* pimpl;

public:
    constexpr Bar() {
        if(std::is_constant_evaluated()) {
            pimpl = &_c;
        }
        else {
            pimpl = &_r;
        }
    }
    
    constexpr int doIt() const {
        return pimpl->doIt();
    }
};

constexpr int foo() {
    Bar bar;
    return bar.doIt();
}

Live

不知道如何称呼这个成语。这有点像类型擦除,但类型并没有真正擦除......构造函数根据执行它的上下文选择要使用(一次)的实现,其他一切都通过接口委托给该实现。

当然,需要注意的是_r 的成员不能在常量表达式中使用是可以的,但构造函数必须是(出于显而易见的原因),所以这就是我说它可能不适用的原因。

【讨论】:

  • 您能否解释或提供一个链接(或我们如何称呼它)来解释此语法的含义“struct : impl”?是来自 C++20 吗?
  • @Fareanor - 不,不是 C++20 stackoverflow.com/questions/7067793/…
  • 哦,谢谢,这是一种匿名派生语法。
  • 感谢您的解决方案,它确实解决了问题,但不幸的是,它对我的​​用例来说开销太大。
  • @geza - 好吧,另一个选项是一个变体。您可以按照我在此处选择实施的方式选择活动成员。应该为您节省一些空间。不过仍然需要间接。
猜你喜欢
  • 2016-10-25
  • 1970-01-01
  • 1970-01-01
  • 2014-07-24
  • 2017-06-25
  • 2020-08-24
  • 2015-05-10
  • 2013-09-13
  • 2018-04-18
相关资源
最近更新 更多