Как выбрать правильную перегрузку шаблона функции?

Я использую libcxx 16.0.0 из проекта LLVM.

В __algorithm/fill_n.h есть следующая функция:

// fill_n isn't specialized for std::memset, because the compiler already optimizes the loop to a call to std::memset.

template <class _OutputIterator, class _Size, class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX20
_OutputIterator
__fill_n(_OutputIterator __first, _Size __n, const _Tp& __value)
{
    for (; __n > 0; ++__first, (void) --__n)
        *__first = __value;
    return __first;
}

Проблема в том, что в моем случае функция не оптимизирована под std::memset. Поэтому я хотел бы сделать эту специализацию вручную.

Я надеялся добавить перегрузку __fil_n для тривиально копируемых _Tp и смежных итераторов (включая итератор std::array).

Итак, учитывая следующий код,

template <typename T, int N> struct foo_t {
  struct iterator {};
};

template <typename T> int f(T) { return 1; }

template <typename T, int N> int f(typename foo_t<T, N>::iterator) { return 2; }

int main() {
  foo_t<int, 10>::iterator a;

  return f(a);
}

Как я могу изменить подписи f, чтобы вторая перегрузка выбиралась при вызове функции f в main ?

Решение с версией C++ >= 11, но <20 было бы идеальным.

На данный момент он не может выбрать вторую перегрузку, потому что вы не указали N. Если вы не хотите вызывать f<N>(a) для получения N, что вы вообще хотите, выбирая вторую перегрузку?

Daniel H 03.07.2024 15:59

Я считаю, что невозможно сделать вывод от типа к окружающему типу. Рассмотрим, например, структура, содержащая typedef, ссылающуюся на структуру iterator из совершенно несвязанной структуры foo_t.

Ulrich Eckhardt 03.07.2024 16:00
typename foo_t<T, N>::iterator — невыведенный контекст. Это xy проблема. То, о чем вы просите напрямую, невозможно, но мы можем помочь вам найти другие пути, если вы расскажете нам о реальной проблеме, а не только о том, что, по вашему мнению, могло бы помочь ее решить. Зачем вам нужны эти две перегрузки?
463035818_is_not_an_ai 03.07.2024 16:01

предположим, что foo_t имел using iterator = int; вместо вложенной структуры, тогда любой foo_t<T, N>::iterator будет того же типа. Обычно это не исключается и не запрещено, поэтому невозможно вывести T и N, учитывая только тип итератора.

463035818_is_not_an_ai 03.07.2024 16:05

Ну, каждый вопрос — это проблема XY, не так ли? :) Основная проблема здесь в том, что std::__fil_n из libcxx 16.0.0 должен быть оптимизирован как memset, но в моем случае это не так. Поэтому я хотел бы добавить перегрузку для std::array::iterator и тривиально копируемых типов, чтобы переписать ее с помощью memset.

Dorian 03.07.2024 16:15

Нет, не каждый вопрос является проблемой xy. Мы называем это проблемой xy, когда на вопрос, который вы задали, нет ответа, но есть большая вероятность, что на вопрос, который вы не задавали, он есть. То, о чем вы просите, невозможно сделать. Вы не можете определить T или N из typename foo_t<T, N>::iterator. Пожалуйста, добавьте информацию о std::array::iterator в вопрос.

463035818_is_not_an_ai 03.07.2024 16:17

Я полагаю, что на самом деле вам нужна специализация для любого T, для которого std::is_trivially_copyable< iterator_traits<T>::value_type> верно.

463035818_is_not_an_ai 03.07.2024 16:22

И, если я правильно понимаю, итератор должен быть ContigiousIterator. Но свойства ContigiousIterator недоступны в C++ <20 :(

Dorian 03.07.2024 16:31

@ 463035818_is_not_an_ai: «То, о чем вы просите напрямую, невозможно» Я изменил f, чтобы его можно было вызывать из main по желанию. Но для std::array<T, N> это было бы невозможно.

Jarod42 03.07.2024 16:37

@Dorian XY-проблема — это жаргонное обозначение ситуации, когда кто-то просит сделать странное или странное Y в (ложном) убеждении, что Y требуется или обычно используется для достижения некоторого X, о котором они не упоминают. Это форма того, что известно в логике как модальная ошибка, часто в сочетании с ошибкой человека в маске, непоследовательностью и т. д.

Swift - Friday Pie 03.07.2024 16:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

с typename foo_t<T, N>::iterator, T и N невыводимы.

friend функция помогает:

template <typename T, int N>
struct foo_t {
  struct iterator {
      friend int f(iterator) { return 2; } // f is no longer template
  };
};

template <typename T> int f(T) { return 1; }

Демо

Если я правильно понимаю, разрешение перегрузки учитывает нешаблонные функции перед шаблонными, а ADL позволяет видеть вложенную функцию f? В любом случае это решает проблему, очень умно.

Dorian 03.07.2024 17:33
f не вложен, он объявлен во внешней области.
Tanveer Badar 04.07.2024 13:16

Вы не можете портативно добавить специализацию std::fill_n. Как правило, вам не разрешено добавлять в пространство имен std (если явно не указано иное, например std::hash).

Однако это не обязательно. Прочитайте комментарий в коде:

// fill_n isn't specialized for std::memset, because the compiler already optimizes the loop to a call to std::memset

Вам не нужно вручную добавлять специализацию. Реализация его не предоставляет, поскольку знает, что если тип можно тривиально скопировать, цикл в существующей специализации будет оптимизирован для использования std::memset.


Еще одна причина, по которой вам не нужно добавлять специализацию или перегрузку std, — это ADL. Для этого я отсылаю вас к этому ответу.


На ваш вопрос о выборе перегрузки...

Здесь:

template <typename T, int N> int f(typename foo_t<T, N>::iterator) { return 2;}

foo_t<T,N>::iterator — невыведенный контекст. Вы не можете определить тип контейнера по типу итератора. Например, std::vector<int>::iterator можно реализовать как простой int*. Однако то же самое справедливо и для std::array<N,int>::iterator. Точнее, нет оснований ожидать, что std::array<N,int>::iterator будет разным для разных N.


Если вы хотите написать функцию, которая делает что-то особенное для итераторов для тривиально копируемых типов, вы можете использовать std::is_trivially_copyable для написания концепции.

#include <type_traits>
#include <iostream>

template <typename T>
concept refers_to_trivially_copyable = std::is_trivially_copyable_v<std::decay_t<decltype(*std::declval<T>())>>;

template <typename T> requires (!refers_to_trivially_copyable<T>)
void foo(const T&) { std::cout << "genereal overload\n"; }

template <refers_to_trivially_copyable T>
void foo(const T&) { std::cout << "trivially copybale\n";}


int main() {
    int* x = nullptr;
    foo(x);
    foo(&std::cout);
}

Вывод :

trivially copybale
genereal overload

Простите меня за многословную нечитаемую концепцию. Полагаю, его можно было бы написать гораздо короче, он просто проверяет, дает ли разыменование экземпляра типа T тип, который можно скопировать через std::memcpy или std::memset.

Спасибо за подробный ответ. Я выпускаю STL, поэтому меня устраивает добавление специализации. К сожалению, код не компилируется, а интерпретируется, поэтому оптимизация memset не сработает, и поэтому мне приходится делать это вручную (в интерпретаторе есть встроенный memset)

Dorian 03.07.2024 18:37

@ Дориан, ок, я ожидал чего-то в этом роде, поскольку ты принял другой ответ. Я подумал, что стоит упомянуть об этом, учитывая, что не все будущие читатели реализуют stl;)

463035818_is_not_an_ai 03.07.2024 18:45

Другие вопросы по теме