Условно неверная функция-член шаблона класса (неявная реализация работает; явная реализация не выполняется) - PullRequest
0 голосов
/ 19 декабря 2018

Я создал шаблон класса.В зависимости от аргументов шаблона он поддерживает разные операции.Шаблон класса должен быть ( неявно ) создан для нескольких комбинаций аргументов шаблона.Для некоторых из этих комбинаций могут быть функции-члены, которые не имеют смысла (а также не компилируются).Однако до тех пор, пока я не применяю явное создание экземпляров, кажется, что все работает так, как нужно.

Теперь есть такие страшные случаи, которые называются "неопределенными", "неопределенными", "плохосформирован; диагностика не требуется ",….Я определенно хочу избежать любой из этих вещей.Поэтому я прошу совета, как справиться с этой ситуацией.

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

#include <iostream>
#include <type_traits>

template<class T>
struct SingleSink {
  SingleSink(T) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

template<class T>
struct DoubleSink {
  DoubleSink(T, T) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

template<class T, int arity /*, some other stuff */>
struct SuperSink {
// This class shall do something special depending on, say, `arity`.
// Instead of partially specializing the whole class template (and introducing
// code duplication for the remaining functionality), let us externalize the 
// `arity`-dependent behavior to a special member.

  using Sink = std::conditional_t<
    arity == 1,
    SingleSink<T>,
    DoubleSink<T>
  >;

  Sink sink_;

// [some more data members that do not depend on `arity`]

// for a fixed `Sink` one of the following constructors should fail to compile
  SuperSink(T i) : sink_{i} {}
  SuperSink(T i, T j) : sink_{i, j} {}
// ... so these are what I call "conditionally invalid member functions".
};

// explicit instantiation yields error (deactivated by comments):
// template struct SuperSink<int, 1>;
// template struct SuperSink<int, 2>;

int main() {
// implicit instantiation works
  SuperSink<int, 1>{5};
  SuperSink<int, 2>{5, 6};

// these yield a compile error (as desired)
// SuperSink<int, 1>{5, 6};
// SuperSink<int, 2>{5};
}
  1. Являются ли эти условно недействительные функции-члены проблемой, если мне никогда не требуется явная реализация?
  2. Если да, то стоит ли проверять работоспособность явной реализации?

Ответы [ 2 ]

0 голосов
/ 20 декабря 2018

Являются ли эти условно недействительные функции-члены проблемой, если мне никогда не требуется явное создание экземпляров?

Даже шаблоны из STL имеют "недопустимые" методы, например: std::vector<T>::resize(std::size_t) с конструкцией, не используемой по умолчаниюT.

Итак, с "недопустимыми" методами ваш класс можно использовать нормально.Задокументируйте ваше требование.

Но тогда эти методы не подходят для SFINAE, поскольку ошибка возникает не в непосредственном контексте, а в инстанцировании.

Вы можете использовать SFINAE по своему усмотрению, чтобыудалите их, если они недействительны, например:

template <std::size_t N = arity, std::enable_if_t<N == 1, int> = 0>
SuperSink(T i) : sink_{i} {}

template <std::size_t N = arity, std::enable_if_t<N != 1, int> = 0>
SuperSink(T i, T j) : sink_{i, j} {}

В C ++ 2a вы можете указать некоторые условия для включения методов в класс (аналогично приведенному выше SFINAE, но с более приятным синтаксисом и без дополнительного шаблона):

SuperSink(T i) requires (arity == 1) : sink_{i} {}

SuperSink(T i, T j) requires (arity != 1) : sink_{i, j} {}
0 голосов
/ 19 декабря 2018

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

static_assert(!std::is_constructible<SuperSink<int, 1>, int, int>::value);

static_assert(!std::is_constructible<SuperSink<int, 2>, int>::value);

Вы можете исправитьэто путем инвертирования схемы: определите SingleSink и DoubleSink как особые случаи (или при необходимости: специализации) универсального SuperSink.

#include <iostream>
#include <type_traits>

template<class T, int arity>
struct SuperSink {

 template<typename... Ts, typename = std::enable_if_t<sizeof...(Ts) == arity> >
  SuperSink(Ts... is) {
       std::cout << __PRETTY_FUNCTION__ << std::endl;
  }

};


template<typename T>
struct SingleSink : SuperSink<T, 1> {
    using Base = SuperSink<T, 1>;
    using Base::Base; // inherit constructor
    // implement special functionality here
};


template<typename T>
struct DoubleSink : SuperSink<T, 2> {
    // follow same pattern as in SingleSink.
}


int main() {
    // implicit instantiation works
    SuperSink<int, 1>{5};
    SuperSink<int, 2>{5, 6};

    // Now, these work as desired
    static_assert(!std::is_constructible<SuperSink<int, 1>, int, int>::value);

    static_assert(!std::is_constructible<SuperSink<int, 2>, int>::value);

    // these yield a compile error (as desired)
    //  SuperSink<int, 1>{5, 6};
    // SuperSink<int, 2>{5};
}
...