需要特定参数包大小的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,D>();
    //auto ptr3 = f<X,E>(); // compile error
    return 0;
}

解决方法

您实际上并不需要概念,只需使用if constexpr

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

如果您确实想要分离的功能,则可以始终使用重载:

template <typename T> // no pack
T* f() {
    return nullptr;
}

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

但是,如果您确实想保持与SFINAE相同的逻辑,则当然可以始终使用requires表达式。应该没有声明的概念,但有可能是(而且足够),但要有约束:

template <typename T,typename...> // pack logically always empty
T* f() {
    return nullptr;
}

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

当然,您也可以将条件包装在一个概念中,但是在这种情况下,使用此技术会收获很少,因为您仍然必须在require子句中使用该概念:

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

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

语法template<my_concept T>将发送T作为第一个参数。您将始终自动获得该参数snet,因此需要将该概念放入require子句中。

,

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

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

the N3690 draft standard起[温度分辨率] 14.6 / 8:

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

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

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

本质上,几乎任何对您的概念的尝试都将导致格式错误,无需诊断的程序。

要在此处查看问题,我们将使用否定重新命名标准:

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

我们可以将此“永久”转换为“存在”。 “ Forall X,P(X)”与“ Not(There X,Not P(X))”相同。

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

如果可变参数模板的require子句要求可变参数模板包为空,则不存在带有空参数包的有效模板。因此,您的模板格式错误,无需进行诊断。


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

该标准的一个问题是,不仅允许它无法编译失败,而且还可以编译实际上可以执行任何操作的程序。

由于允许编译器执行此操作,并且 other 情况下的某些优化实际上导致了这种情况的发生,因此应避免编写格式错误,不需要诊断的代码,例如瘟疫。


一种解决方法是:

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...>();
}