Я использую 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 было бы идеальным.
Я считаю, что невозможно сделать вывод от типа к окружающему типу. Рассмотрим, например, структура, содержащая typedef, ссылающуюся на структуру iterator
из совершенно несвязанной структуры foo_t
.
typename foo_t<T, N>::iterator
— невыведенный контекст. Это xy проблема. То, о чем вы просите напрямую, невозможно, но мы можем помочь вам найти другие пути, если вы расскажете нам о реальной проблеме, а не только о том, что, по вашему мнению, могло бы помочь ее решить. Зачем вам нужны эти две перегрузки?
предположим, что foo_t
имел using iterator = int;
вместо вложенной структуры, тогда любой foo_t<T, N>::iterator
будет того же типа. Обычно это не исключается и не запрещено, поэтому невозможно вывести T
и N
, учитывая только тип итератора.
Ну, каждый вопрос — это проблема XY, не так ли? :) Основная проблема здесь в том, что std::__fil_n
из libcxx
16.0.0 должен быть оптимизирован как memset
, но в моем случае это не так. Поэтому я хотел бы добавить перегрузку для std::array::iterator и тривиально копируемых типов, чтобы переписать ее с помощью memset.
Нет, не каждый вопрос является проблемой xy. Мы называем это проблемой xy, когда на вопрос, который вы задали, нет ответа, но есть большая вероятность, что на вопрос, который вы не задавали, он есть. То, о чем вы просите, невозможно сделать. Вы не можете определить T
или N
из typename foo_t<T, N>::iterator
. Пожалуйста, добавьте информацию о std::array::iterator
в вопрос.
Я полагаю, что на самом деле вам нужна специализация для любого T
, для которого std::is_trivially_copyable< iterator_traits<T>::value_type>
верно.
И, если я правильно понимаю, итератор должен быть ContigiousIterator. Но свойства ContigiousIterator недоступны в C++ <20 :(
@ 463035818_is_not_an_ai: «То, о чем вы просите напрямую, невозможно» Я изменил f
, чтобы его можно было вызывать из main
по желанию. Но для std::array<T, N>
это было бы невозможно.
@Dorian XY-проблема — это жаргонное обозначение ситуации, когда кто-то просит сделать странное или странное Y в (ложном) убеждении, что Y требуется или обычно используется для достижения некоторого X, о котором они не упоминают. Это форма того, что известно в логике как модальная ошибка, часто в сочетании с ошибкой человека в маске, непоследовательностью и т. д.
с 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
? В любом случае это решает проблему, очень умно.
f
не вложен, он объявлен во внешней области.
Вы не можете портативно добавить специализацию 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)
@ Дориан, ок, я ожидал чего-то в этом роде, поскольку ты принял другой ответ. Я подумал, что стоит упомянуть об этом, учитывая, что не все будущие читатели реализуют stl;)
На данный момент он не может выбрать вторую перегрузку, потому что вы не указали
N
. Если вы не хотите вызыватьf<N>(a)
для полученияN
, что вы вообще хотите, выбирая вторую перегрузку?