17

if constexpr是摆脱 C++ 程序中的预处理器的一大步。但是它只在函数中起作用——就像在这个例子中:

enum class OS
{
    Linux,
    MacOs,
    MsWindows,
    Unknown
};

#if defined(__APPLE__)
constexpr OS os = OS::MacOs;
#elif defined(__MINGW32__)
constexpr OS os = OS::MsWindows;
#elif defined(__linux__)
constexpr OS os = OS::Linux;
#else
constexpr OS os = OS::Unknown;
#endif

void printSystem()    
{
    if constexpr (os == OS::Linux)
    {
        std::cout << "Linux";
    }
    else if constexpr (os == OS::MacOs)
    {
        std::cout << "MacOS";
    }
    else if constexpr (os == OS::MsWindows)
    {
        std::cout << "MS Windows";
    }
    else
    {
        std::cout << "Unknown-OS";
    }
}

但是摆脱预处理器的梦想并不十分满足——因为以下示例无法编译:

1不能在类定义中使用它来不同地定义类的某些成员:

class OsProperties
{
public:
    static void printName()
    {
        std::cout << osName;
    }
private:
    if constexpr (os == OS::Linux)
    {
        const char* const osName = "Linux";
    }
    else if constexpr (os == OS::MacOs)
    {
        const char* const osName = "MacOS";
    }
    else if constexpr (os == OS::MsWindows)
    {
        const char* const osName = "MS Windows";
    }
    else
    {
        const char* const osName = "Unknown";
    }
};

2它也不适用于非类范围(如全局范围):

if constexpr (os == OS::Linux)
{
    const char* const osName = "Linux";
}
else if constexpr (os == OS::MacOs)
{
    const char* const osName = "MacOS";
}
else if constexpr (os == OS::MsWindows)
{
    const char* const osName = "MS Windows";
}
else
{
    const char* const osName = "Unknown";
}

我(几乎)确定这符合 C++17 规范,if constexpr仅适用于函数体 - 但我的问题是:

Q1如何在 C++1z/C++14 中实现类和全局作用域类似的效果if-constexpr?而且我不是在这里要求对模板专业化的另一种解释...但是具有与if constexpr...类似的简单性的东西

Q2是否有针对上述范围扩展 C++ 的计划?

4

3 回答 3

16

如何在函数中实现类似 if-constexpr 的效果 - 对于 C++1z/C++14 中的类和全局范围?而且我不是在这里要求对模板专业化的另一种解释......

你基本上只是说,“我想要模板专业化,但没有那些讨厌的模板专业化。”

if constexpr是根据编译时构造改变函数行为的工具。模板专业化是 C++ 提供的用于根据编译时构造更改定义的工具。它是 C++ 为该功能提供的唯一工具。

现在对于初始化变量的简单案例,您始终可以创建和调用 lambda。C++17 提供constexpr对 lambdas 的支持,并且 lambda 可以if constexpr用来决定返回什么值。

是否有任何计划为上述范围扩展 C++?

,这是所有的建议,过去几年的建议都没有深入到这个领域。

他们极不可能做到。

于 2016-12-13T16:06:04.473 回答
4

索引类型:

template<std::size_t I>
using index = std::integral_constant<std::size_t, I>;

first_truth采用一组编译时布尔值,并说明第一个布尔值在编译时的索引是什么。如果你传递 N 个编译时布尔值,如果全部为假,则返回 N:

constexpr index<0> first_truth() { return {}; }
template<class...Rest>
constexpr index<0> first_truth(std::true_type, Rest...) { return {}; }
template<class...Rest>
constexpr auto first_truth(std::false_type, Rest...rest) {
  return index<first_truth( rest... )+1>{};
}

dispatch接受一组编译时布尔值并返回一个 lambda。此 lambda 通过完美转发与第一个真正的编译时间 bool 匹配的第一个元素返回:

template<class...Bools>
constexpr auto dispatch(Bools...bools) {
  constexpr auto index = first_truth(bools...);

  return [](auto&&...fs){
    return std::get< decltype(index){} >(
      std::forward_as_tuple( decltype(fs)(fs)... )
    );
  };
}

编译时 bool 类型:

template<bool b>
using bool_t = std::integral_constant<bool, b>;
template<bool b>
bool_t<b> bool_k{};

现在我们解决您的问题:

const char* const osName = 
  dispatch(
    bool_k<os == OS::Linux>,
    bool_k<os == OS::MacOs>,
    bool_k<os == OS::MsWindows>
  )(
    "Linux",
    "MacOS",
    "MS Windows",
    "Unknown"
  );

这应该近似于编译时开关。我们可以通过更多的工作将布尔值更紧密地与论点联系起来。

代码未编译,可能包含 tpyos。

于 2016-12-13T18:47:15.520 回答
2

如何基于一些编译时间常数来定义不同的类型而没有模板专业化?

这里是:

constexpr auto osPropsCreate()
{
    if constexpr (os == OS::Linux) {
        struct Props { const char* name; int props1; using handle = int; }; 
        return Props{"linux", 3};
    } else if constexpr (os == OS::MacOs) {
        struct Props { const char* name; using handle = float; }; 
        return Props{"mac"};
    } else if constexpr (os == OS::MsWindows) {
        struct Props { const char* name; using handle = int; }; 
        return Props{"win"};
    } else
        return;  
}

using OsProps = decltype(osPropsCreate());
constexpr OsProps osProps = osPropsCreate();

如您所见 - 我使用新构造if constexpr从一些“实现”函数中生成依赖于编译时间常数的类型。它不像static if在 D 语言中那样容易使用——但它有效——我可以做到:

int linuxSpecific[osProps.props1];
int main() {
    std::cout << osProps.name << std::endl;
    OsProps::handle systemSpecificHandle;
}

接下来 - 根据编译时间常数定义不同的函数:

constexpr auto osGetNameCreate() {
    if constexpr (os == OS::Linux) {
        struct Definition {
            static constexpr auto getName() {
                return "linux";
            }
        };
        return Definition::getName;
    } else if constexpr (os == OS::MacOs) {
        // we might use lambda as well
        return [] { return "mac"; };
    } else if constexpr (os == OS::MsWindows) {
        struct Definition {
            static constexpr auto getName() {
                return "win";
            }
        };
        return Definition::getName;
    } else
        return;
}


constexpr auto osGetName = osGetNameCreate();

int main() {
    std::cout << osGetName() << std::endl;
} 

实际上,它们可以是类函数对象(函子),也可以是嵌套类中的静态成员函数。这无关紧要——一个人可以完全自由地为不同的编译时间常数(在这种情况下是 OS 类型)定义不同的东西。注意,对于未知系统,我们只是返回void- 它会导致未知系统的编译错误......


回答第二个问题:

第一个答案在评论中提供了推理(链接)。我的解释是 C++ 标准委员会还没有为这种变化做好准备。也许与 D 竞争将/将是再次提出这个主题的好理由......

于 2016-12-14T16:25:45.777 回答