Обеспечение того, чтобы тип аргумента шаблона соответствовал типу его вариационного конструктора - PullRequest
2 голосов
/ 26 мая 2019

Я хотел бы иметь такой класс:

template<typename T>
struct Foo {
    T* data_;

    template<typename... Ts, std::enable_if<std::is_same<T,Ts>...>...>
    explicit Foo(Ts...ts) : data_{ ts... } {}
};

Тем не менее, что-то с синтаксисом неверно, и я не уверен, что вы можете установить параметры в указатель напрямую, как это при инициализации.

Я бы хотел, чтобы это было просто так:

Foo<int> f1{ 1, 3, 5, 7 }; // Or
// Foo<int> f1( 1, 3, 5 7 );
// f1.data_[0] = 1
// f1.data_[1] = 3
// f1.data_[2] = 5
// f1.data_[3] = 7
// f1.data_[4] = ... not our memory either garbage or undefined...

Foo<float> f2{ 3.5f, 7.2f, 9.8f }; // Or
// Foo<float> f2( 3.5f, 7.2f, 9.8f );
// f2.data_[0] = 3.5
// f2.data_[1] = 7.2
// f2.data_[2] = 9.8
// f2.data_[3] = ... not our memory

Я также хотел бы проверить конструктор, чтобы убедиться, что каждый передаваемый в конструктор параметр имеет тип <T>; Проще говоря, для каждого Ts это должно быть T.

Возможно, я переосмысливаю это, но за свою жизнь я не могу получить это или что-то похожее на компиляцию. Я не знаю, находится ли он внутри enable_if, is_same или через список инициализаторов класса и пытается сохранить содержимое в указателе. Я не знаю, должен ли я вместо этого использовать массив T, но размер массива не будет известен, пока аргументы не будут переданы в конструктор. Я также пытаюсь сделать это без использования основного контейнера, такого как std::vector; это больше для самообразования, чем для практического исходного кода. Я просто хочу посмотреть, как это можно сделать с помощью необработанных указателей.


Редактировать

Я изменил свой класс на что-то вроде этого:

template<typename T>
struct Foo {
    T* data_;

    template<typename... Ts, std::enable_if_t<std::is_same<T, Ts...>::value>* = nullptr>
    explicit Foo( const Ts&&... ts ) : data_{ std::move(ts)... } {}
 };

И при попытке его использовать:

 int a = 1, b = 3, c = 5, d = 7;
 Foo<int> f1( a, b, c, d );
 Foo<int> f2{ a, b, c, d };

Я немного ближе к этой итерации; но они оба дают разные ошибки компилятора.

  • Первое из них: C2661: "Никакая перегруженная функция не принимает 4 аргумента"
  • И второе: C2440: "инициализация, невозможно преобразовать список инициализатора в контейнер, ни один конструктор не может принять тип источника, или разрешение перегрузки конструктора было неоднозначным."

Ответы [ 4 ]

4 голосов
/ 26 мая 2019

Почему бы просто не использовать std::initialize_list:?

#include <iostream>
#include <type_traits>
#include <vector>

template <class T>
struct Foo
{
  std::vector<T> data_;

  explicit Foo(std::initializer_list<T> data) : data_(data)
  {
    std::cout << "1";
  };

  template <typename... Ts,
            typename ENABLE=std::enable_if_t<(std::is_same_v<T,Ts> && ...)> >
  explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...})
  {
    std::cout << "2";
  }
};

int main()
{
  Foo<int> f1{1, 3, 5, 7}; // prints 1
  Foo<int> f2(1, 3, 5, 7); // prints 1 then 2

  return 0;
}

Если некоторые Ts отличаются от T, вы получите ошибку во время компиляции.

С

gcc -std=c++17  prog.cpp  

вы получите:

  Foo<int> f1{1, 3, 5., 7};

ошибка: сужение преобразования "5.0e + 0" из "double" в "int" внутри {} [-Звонок] Foo f1 {1, 3, 5., 7}; ^

и

Foo<int> f2(1, 3, 5., 7);

вы получите

ошибка: нет подходящей функции для вызова call Foo :: Foo (int, int, double, int) ’Foo f2 (1, 3, 5., 7); ^ примечание: кандидат: ‘шаблон Foo :: Foo (Ts ...)’ явное Foo (Ts ... ts): Foo (СТД :: initializer_list {ц ...})

...

Обновление: , если вы действительно хотите использовать что-то вроде необработанный указатель , вот полный рабочий пример:

#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>

template <class T>
struct Foo
{
  size_t n_;
  std::unique_ptr<T[]> data_;

  explicit Foo(std::initializer_list<T> data) : n_(data.size()), data_(new T[n_])
  {
    std::copy(data.begin(), data.end(), data_.get());
    std::cout << "1";
  };

  template <typename... Ts, typename ENABLE = std::enable_if_t<(std::is_same_v<T, Ts> && ...)> >
  explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...})
  {
    std::cout << "2";
  }

  friend std::ostream& operator<<(std::ostream& out, const Foo<T>& toPrint)
  {
    for (size_t i = 0; i < toPrint.n_; i++)
      std::cout << "\n" << toPrint.data_[i];
    return out;
  }
};

int main()
{
  Foo<int> f1{1, 3, 5, 7};  // prints 1
  Foo<int> f2(1, 3, 5, 7);  // prints 1,2

  std::cout << f1;
  std::cout << f2;

  return 0;
}

Я позволю вам заменить unique_ptr необработанным указателем на всю дополнительную работу: delete [] etc ...

2 голосов
/ 26 мая 2019

std::is_same сравнивает только два типа, и вы не можете использовать расширения пакета для объявления нескольких параметров шаблона.Это означает, что вам нужно вытащить все свои std::is_same чеки в другую проверку:

template <typename T, typename... Ts>
struct all_same : std::bool_constant<(std::is_same<T, Ts>::value && ...)> {};

template <typename T>
struct Foo
{
    std::vector<T> data_;

    template <typename... Ts, std::enable_if_t<all_same<T, std::decay_t<Ts>...>::value>* = nullptr>
    Foo(Ts&&... ts)
        : data_{std::forward<Ts>(ts)...}
    {
    }
};

Live Demo

Вам также необходимо выделить память дляваш data_ массив.Здесь я использовал std::vector, чтобы позаботиться об этом распределении для меня, но вы можете использовать new[] и delete[], чтобы управлять им самостоятельно, если вы действительно хотите.

1 голос
/ 26 мая 2019

enable_if и is_same ничего не будут хранить нигде, они являются только конструкциями времени компиляции и не уступают никакому коду в двоичном исполняемом файле.

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

Либо Foo владеет областью памяти и должен выделять ее в конструкторе и удалять в деструкторе (если есть сомнения: используйте std::vector!), Либо псевдоним некоторой внешней памяти и должен получать указатель на эту память.

Теперь о синтаксисе:

  • std::is_same - это шаблон, который предоставляет логическую константу value и должен использоваться следующим образом: std::is_same<T1, T2>::value.В качестве альтернативы вы можете использовать std::is_same_v<T1, T2>.
  • std::enable_if обеспечивает член типа type, только если константное выражение (1-й параметр шаблона) имеет значение true.Используйте это как std::enable_if<expr, T>::type.Если expr равно true, type является typedef для T.В противном случае он не определен и приводит к ошибке замещения.В качестве альтернативы вы можете использовать std::enable_if_t<expr, T>

Вы можете посмотреть здесь для аналогичного вашего подхода.

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

#include <string>
#include <vector>
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T>
struct Foo {
    vector<T> data_;

    template<typename ...Ts>
    explicit Foo(Ts... ts) : data_{ ts... } {}

    void print() {
        for (const auto &v : data_) {
            cout << v << " ";
        }
        cout << endl;
    }
};

int main() {

    Foo<int> ints { 1, 2, 3, 4, 5 };
    Foo<string> strings { "a", "b", "c", "d", "e"};
    // Foo<string> incorrect { "a", 2, "c", 4, "e"};

    ints.print();
    strings.print();
    // incorrect.print();

    return 0;
}
0 голосов
/ 27 мая 2019

Самый идиоматический способ сделать это в C ++ 17 - использовать std::cunjunction_v.Он лениво оценивает последующие значения и позволяет избежать выражений сгиба.Также сообщения, сгенерированные компилятором, одинаковы для обоих случаев, которые вы упомянули в отредактированном фрагменте кода.

Кроме того, передача данных с помощью const-rvalue-ref не имеет смысла, поскольку невозможно перемещать данные из const-объектов.Кроме того, вы перемещали пакет аргументов к указателю.Я не знал, что с этим делать, поэтому просто убрал его.

Дополнительно, посмотрите на std::decay_t - без него это не сработало бы, так как иногда Ts выводится не как int, а как const int & или int &.Это приводит к тому, что std::is_same_v ложно.

Приведенный ниже код прекрасно компилируется в Godbolt:

template<typename T>
struct Foo {
    T* data_;

    template<
        typename... Ts,
        std::enable_if_t<
            std::conjunction_v<
                std::is_same<T, std::decay_t<Ts>>...
            >
        > * = nullptr
    >
    explicit Foo( Ts&&... ts ) : data_{ } {}
 };
...