Концепция шаблона C ++, требующая определенного размера пакета параметров

Изменить: эта функция должна проверять типы один за другим и возвращать obj любого, который удовлетворяет условию или nullptr.

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 больше не нужна. И для первого шаблона, естественно, требуется 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;
}

person Krzysztof Sommerfeld    schedule 24.08.2020    source источник
comment
Отсутствует T для первой перегрузки.   -  person Barry    schedule 24.08.2020
comment
@Barry Нет, я думаю, он хочет, чтобы в первом не было T. Он хочет, чтобы случай с нулевым аргументом возвращал nullptr, а не случай с одним аргументом.   -  person Yakk - Adam Nevraumont    schedule 24.08.2020
comment
@ Yakk-AdamNevraumont Я действительно не знаю, что намеревается сделать OP, но в написанном коде есть T, но здесь нет типа с именем T. Так что либо где-то должен быть объявлен T, либо функция должна возвращать nullptr_t.   -  person Barry    schedule 24.08.2020


Ответы (2)


Для этого вам не нужны концепции, просто используйте 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...>();
}

Но, конечно, вы всегда можете использовать requires выражения, если действительно хотите сохранить ту же логику, что и в SFINAE. Это должно быть возможно (и достаточно) без объявленной концепции, но только с ограничением:

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...> // pack logically always empty
T* f() {
    return nullptr;
}

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 автоматически, поэтому необходимо поместить концепцию в раздел requires.

person Guillaume Racicot    schedule 24.08.2020
comment
Требуется ли второй пункт requires? Разве неограниченный не должен быть менее специализированным и, следовательно, выбираться только в том случае, если другой - нет? - person Nicol Bolas; 24.08.2020
comment
@NicolBolas Я не был уверен в этом, спасибо за пояснение. - person Guillaume Racicot; 24.08.2020
comment
См. ответ ниже. Думаю, концептуальное решение неверно. - person cigien; 24.08.2020
comment
Да, сейчас вроде нормально, хотя я не совсем уверен. - person cigien; 24.08.2020
comment
@KrzysztofSommerfeld Я прошу не согласиться - person Guillaume Racicot; 24.08.2020
comment
Если вы вызовете f () ‹Derived1, Derived2, Derived3› (), вы закончите проверку, если условие только для Derived1 и Derived2; Добавлена ​​информация о том, что T должен быть базой для всех других типов; - person Krzysztof Sommerfeld; 24.08.2020
comment
@KrzysztofSommerfeld, тогда это совсем другая проблема. Я немного сбит с толку, почему мой ответ не разрешил то, что вы публиковали раньше, и теперь мне сложно понять, почему классы с разными базами связаны с размером пакета. - person Guillaume Racicot; 24.08.2020
comment
Эта функция должна проверять типы один за другим и возвращать любой объект, который удовлетворяет условию или nullptr. Добавлено описание. - person Krzysztof Sommerfeld; 24.08.2020
comment
И я знаю, что вы можете сделать это 10 разными способами. Я спросил, как можно использовать концепцию для этого, если это возможно. Как и мой пример с недопустимым кодом. - person Krzysztof Sommerfeld; 24.08.2020
comment
@KrzysztofSommerfeld Думаю, я понял, о чем вы. Скажите, если это то, что вы искали. - person Guillaume Racicot; 24.08.2020
comment
По-прежнему нет, но свою идею легко исправить. Теперь вы пропустите последний тип (поскольку последний аргумент также равен 0), поэтому вы вернете nullptr. Но вы можете сделать требование EmptyPack и использовать его таким образом. Тогда он работает, выглядит красиво и легко читается. - person Krzysztof Sommerfeld; 25.08.2020

Любой пакет параметров шаблона, который имеет допустимые экземпляры только для пакетов размера 0, делает вашу программу некорректной, и диагностику не требуется.

Это относится к вашему первому рабочему примеру, а также к любому практическому варианту использования концепций, который я могу придумать.

Из чернового варианта стандарта N3690 [temp.res] 14.6 / 8:

Если для каждой допустимой специализации вариативного шаблона требуется пустой пакет параметров шаблона, шаблон имеет неправильный формат, и диагностика не требуется.

(Я видел, что во многих версиях C ++ я просто использую случайный черновик стандарта, как он обнаружился, когда я гуглил стандартный PDF-файл C ++.)

Обратите внимание, что (а) ваши два f являются перегрузками, а не специализациями, и (б) то, что программисты C ++ подразумевают под допустимой специализацией шаблона и что стандартные средства, не совсем одно и то же.

По сути, практически любая попытка реализации вашей концепции приведет к созданию плохо сформированных программ, не требующих диагностики.

Чтобы увидеть здесь проблему, мы перефразируем стандарт, используя отрицание:

Если (каждая допустимая специализация вариативного шаблона требует пустого пакета параметров шаблона), то (шаблон неверно сформирован, диагностика не требуется).

Мы можем преобразовать все это в существующее. Для всех X P (X) совпадает с Not (существует 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...>();
}
person Yakk - Adam Nevraumont    schedule 24.08.2020
comment
Я не понимаю, как это применимо здесь. Совершенно верно оценивать концепцию с непустым пакетом, и аналогичным образом невыполнение ограничений не означает, что шаблон недействителен. - person T.C.; 24.08.2020
comment
@ T.C. Назовите допустимую специализацию шаблона, в котором находится предложение requires, для которого не требуется пустой пакет параметров шаблона. Это отрицание плохо сформированного требования, не требующего диагностики: существует допустимая специализация вариативного шаблона с непустым пакетом параметров, иначе шаблон плохо сформирован, диагностика не требуется. - person Yakk - Adam Nevraumont; 24.08.2020
comment
Я как бы думаю, что здесь речь идет больше о недопустимых специализациях - в форме примеров temp.res с operator++, union X и struct A - реализации которых могут диагностировать недопустимые специализации. Здесь нет недействительных специализаций. - person Barry; 24.08.2020
comment
@Barry Как я уже упоминал, стандартное определение специализации отличается от нашего разговорного. Если вы читаете его как экземпляр, он ближе к нестандартному. Допустимая специализация означает случай, когда вы передали фиксированные параметры шаблона, и результат действителен. Это требование распространяется как на основной шаблон, так и на второстепенные (для классов). (=delete допустимо, но тело, которое не компилируется, нет) Для функций существует перегрузка, а не вторичные шаблоны. - person Yakk - Adam Nevraumont; 24.08.2020