首页 > 解决方案 > 需要特定参数包大小的 C++ 模板概念

问题描述

编辑:这个函数必须一一检查类型并返回任何满足条件或 nullptr 的 obj。

template <typename... Args, typename = std::enable_if_t<(sizeof...(Args) == 0)>()>
std::nullptr_t f() { return nullptr; }

template <typename T, typename... Args>
BaseClassOfAllArgs* f() {
    if (<some condition related to T...>)
        return new T;
    return f<Args...>();
}

这段代码对我有用。但我想知道是否可以重写这段代码来使用概念?我的意思是这样的:

template <typename... Args>
concept NoArgs = (sizeof...(Args) == 0);

然后使用它代替 std::enable_if(此代码不起作用)

template <NoArgs Args>
std::nullptr_t f() { return nullptr; }

template <typename T, typename... Args>
BaseClassOfAllArgs* f() {
    if (<some condition related to T...>)
        return new T;
    return f<Args...>();
}

编辑:在从评论中的人那里获得一些提示后,这是代码的工作示例。在我将“Base”添加到模板后,事实证明不再需要 EmptyPack 概念。而 First 模板自然需要 3 个类型名。但是,我不确定 EmptyPack 这个概念。它真的让我的程序格式不正确,不需要诊断吗?

#include <iostream>
#include <type_traits>
#include <typeinfo>
class X {};

class A : public X {};
class B : public X {};
class C : public X {};
class D : public C {};
class E {};

template<class T, class... Args >
concept DerivedsOfBase = (std::is_base_of_v<T, Args> && ...);

template<typename... Args>
concept EmptyPack = sizeof...(Args) == 0;

template<typename T>
std::nullptr_t f() {
    std::cout << "End of the types list" << std::endl;
    return nullptr;
}

template<typename Base, typename T, typename... Args> requires DerivedsOfBase<Base, T, Args...>
Base* f() {
    std::cout << typeid(T).name() << std::endl;
    if (<some condition related to T>)
        return new T;
    return f<Base, Args...>();
}

int main()
{
    auto ptr = f<X, A, B, C>();
    auto ptr2 = f<X, A, B, D>();
    //auto ptr3 = f<X, A, B, E>(); // compile error
    return 0;
}

标签: c++c++20c++-concepts

解决方案


任何仅具有大小为 0 的包的有效实例化的模板参数包都会使您的程序格式错误,无需诊断。

这适用于您的第一个“工作”示例,以及我能想到的使用概念的任何实际变体。

来自N3690 草案标准[temp.res] 14.6/8:

如果可变参数模板的每个有效特化都需要一个空模板参数包,则该模板格式错误,不需要诊断。

(我已经看到在许多版本的 C++ 中,我只是使用随机的标准草案,因为它在我搜索 C++ 标准 pdf 时出现。)

请注意,(a)您的两个f是重载而不是专业化,以及(b)C++ 程序员所说的“模板的有效专业化”的含义与标准的含义并不完全相同。

从本质上讲,几乎任何对您的概念的尝试都会导致格式错误、不需要诊断的程序。

为了看到这里的问题,我们将使用否定改写标准:

如果(可变参数模板的每个有效特化都需要一个空模板参数包)则(模板格式错误,不需要诊断)。

我们可以将这个“forall”转换为“there exists”。"Forall X, P(X)" 与 "Not( There exists X, Not P(X) )" 相同。

除非(存在具有非空模板参数包的可变参数模板的有效特化),否则(模板格式错误,不需要诊断)。

如果可变参数模板具有要求可变参数模板包为空的 requires 子句,则不存在具有空参数包的该模板的有效特化。所以你的模板格式不正确,不需要诊断。


通常,存在此类规则,以便编译器可以检查您的代码是否总是无意义的。诸如没有类型参数可以使其编译的模板或必须为空的包之类的东西通常表明您的代码存在错误。通过使其格式错误并且不需要诊断,编译器可以发出诊断并且无法编译。

我对标准的一个问题是,它不仅允许编译失败,而且允许编译一个实际上可以做任何事情的程序。

因为编译器被允许这样做,而在其他情况下的一些优化实际上会导致这种情况发生,所以你应该避免编写格式不正确、不需要诊断的代码,就像瘟疫一样。


一种解决方法是:

namespace implementation_details {
  struct never_use_me;
}
template <class=implementation_details::never_use_me>
std::nullptr_t f() { return nullptr; }

template <typename T, typename... Args>
T* f() {
  if (<some condition related to T...>)
    return new T;
  return f<Args...>();
}

另一种选择是:

template <typename T, typename... Args>
T* f() {
  if (<some condition related to T...>)
    return new T;
  if constexpr (sizeof...(Args)==0)
    return nullptr;
  else
    return f<Args...>();
}

推荐阅读