Вывод параметра шаблона аргумента шаблонного класса: постоянная проблема - PullRequest
3 голосов
/ 29 мая 2019

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

Скажем, у меня есть функция, подпись которой

template<typename T>
void foo(const MyArray<const T>& x);

Значение const в параметре шаблона не позволяет мне изменять содержимое массива, поскольку (по причинам, не входящим в этот вопрос), методы доступа ([] и ()) для MyArray<T> всегда помечаются как const и возвращают ссылки на T (следовательно, const обеспечивает безопасность, поскольку MyArray<T>::operator[] возвращает T&, а MyArray<const T>::operator[] возвращает const T&).

Отлично.Однако шаблоны с различными аргументами шаблона не связаны, поэтому я не могу связать ссылку на MyClass<T> со ссылкой MyClass<const T>, то есть я не могу сделать это

MyArray<double> ar(/*blah*/);
foo(ar);

Обратите внимание, что,без ссылки приведенный выше код будет работать при условии , что существует конструктор копирования, который позволяет мне создавать MyArray<const T> из MyArray<T>.Однако я не хочу удалять ссылку, так как построение массива будет происходить много раз , и, несмотря на относительно низкую стоимость, его стоимость будет складываться.

Так что вопрос: как я могу вызвать foo с MyArray<T>?

Мое единственное решение на данный момент таково:

MyArray<T> ar(/*blah*/);
foo(reinterpret_cast<MyArray<const T>>(ar));

(на самом деле в моем коде я скрыл переинтерпретацию ввстроенная функция с более подробным названием, но конец игры тот же).Класс MyArray не имеет специализации для const T, что делает его не подлежащим повторному толкованию, поэтому приведение должно быть «безопасным».Но это не очень хорошее решение для чтения.Альтернативой может быть дублирование подписи foo, чтобы иметь версию, принимающую MyArray<T>, реализация которой выполняет приведение и вызывает версию const.Проблема в этом заключается в дублировании кода (и у меня довольно много функций foo, которые нужно дублировать).

Возможно, какая-то дополнительная магия шаблона на подписи foo?Цель состоит в том, чтобы передать оба значения MyArray<T> и MyArray<const T>, сохраняя при этом постоянную корректность (то есть заставить компилятор лаять, если я случайно изменил ввод в теле функции).

Редактировать 1: класс MyArray (реализация которого не находится под моим контролем) имеет методы доступа const, поскольку он хранит указатели.Таким образом, вызов v[3] изменит значения в массиве, но не члены , хранящиеся в классе (а именно, указатель и некоторые метаданные, подобные интеллектуальному указателю).Другими словами, объект де-факто не модифицирован средствами доступа, хотя массив есть.Это семантическое различие.Не уверен, почему они пошли в этом направлении (у меня есть идея, но слишком долго объяснять).

Редактировать 2 : Я принял один из двух ответов (хотя они были несколько похожи).Я не уверен (по причинам, которые долго объяснять), что класс-оболочка выполним в моем случае (возможно, я должен подумать об этом).Я также озадачен тем фактом, что, хотя

template<typename T>
void foo(const MyArray<const T>& x);
MyArray<int> a;
foo(a);

не компилируется, следующее делает

void foo(const MyArray<const int>& x);
MyArray<int> a;
foo(a);

Примечание: MyArray предлагает шаблонный «конструктор копирования» с подписью

template<typename S>
MyArray(const MyArray<S>&);

, поэтому он может создать MyArray<const T> из MyArray<T>.Я озадачен, почему это работает, когда T явно, в то время как это не работает, если T является параметром шаблона.

Ответы [ 3 ]

3 голосов
/ 30 мая 2019

Я бы остался с

template<typename T>
void foo(const MyArray<T>&);

и обязательно создайте его экземпляр с помощью const T (например, в unitTest).

В противном случае вы можете создать представление как std::span.

Примерно так (в зависимости от других методов, предоставляемых MyArray, вы, вероятно, можете сделать лучший просмотр констант. В настоящее время я использовал только operator[]):

template <typename T>
struct MyArrayConstView
{
    MyArrayConstView(MyArray<T>& array) : mArray(std::ref(array)) {}
    MyArrayConstView(MyArray<const T>& array) : mArray(std::ref(array)) {}

    const T& operator[](std::size_t i) {
        return std::visit([i](const auto& a) -> const T& { return a[i]; }), mArray);
    }

private:
    std::variant<std::reference_wrapper<MyArray<T>>,
                 std::reference_wrapper<MyArray<const T>>> mArray;
};

, а затем

template <typename T>
void foo(const MyArrayConstView<T>&);

но вам нужно вызвать его явно (вычитание не произойдет, поскольку MyArray<T> не является MyArrayConstView)

MyArray<double> ar(/*blah*/);
foo(MyArrayConstView{ar});
foo<double>(ar);
2 голосов
/ 30 мая 2019

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

template <typename From, typename To>
struct xfer_refs_cv
{
    using type = To;
};
template <typename From, typename To>
struct xfer_refs_cv<const From, To>
{
    using type = const typename xfer_refs_cv<From, To>::type;
};
template <typename From, typename To>
struct xfer_refs_cv<volatile From, To>
{
    using type = volatile typename xfer_refs_cv<From, To>::type;
};
template <typename From, typename To>
struct xfer_refs_cv<From&, To>
{
    using type = typename xfer_refs_cv<From, To>::type&;
};
template <typename From, typename To>
struct xfer_refs_cv<From&&, To>
{
    using type = typename xfer_refs_cv<From, To>::type&&;
};

template <typename CheckType, typename Func, typename CallType>
constexpr decltype(auto) check_and_call(Func&& f, CallType&& call_arg)
    noexcept(noexcept(std::forward<Func>(f)(std::forward<CallType>(call_arg))))
{
    (void) decltype(std::declval<Func&&>()
      (std::declval<typename xfer_refs_cv<CallType&&, CheckType>::type>()), 0){};
    return std::forward<Func>(f)(std::forward<CallType>(call_arg));
}

template<typename T>
void foo(const MyArray<T>& x)
{
    check_and_call<MyArray<const T>>(
        [](auto&& x) {
        // Function implementation here.
    }, x);
}
2 голосов
/ 30 мая 2019

Поскольку вам не разрешено изменять MyArray, одним из вариантов является использование класса адаптера.

template <typename T>
class ConstMyArrayView {
   public:
    // Not an explicit constructor!
    ConstMyArrayView(const MyArray<T>& a) : a_(a) {}
    const T& operator[](size_t i) const { return a_[i]; }
   private:
     const MyArray<T>& a_;
};

template<typename T>
void foo(const ConstMyArrayView<T>& x);

MyArray<T> x;
foo(x);

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

...