Dorian
Dorian

Reputation: 596

How to select the right overload of function template?

I'm using libcxx 16.0.0 from the LLVM project.

In __algorithm/fill_n.h, there is the following function:

// 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;
}

The problem is that in my case the function is not optimized to std::memset. So I would like to do this specialization manually.

I was hoping to add an overload of __fil_n for trivially copyable _Tp and contiguous iterators (including std::array's iterator).

So, given the following code,

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);
}

How can I change the signatures of f so that the second overload is selected at the f function call in main ?

A solution with a C++ version >= 11 but <20 would be ideal.

Upvotes: 0

Views: 93

Answers (2)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122830

You cannot portably add a specialization of std::fill_n. Generally you are not allowed to add to namespace std (unless it is explicitly stated otherwise, for example std::hash).

However, you do not have to. Read the comment in the code:

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

You do not have to manually add a specialization. The implementation does not provide one, because it knows that when the type is trivially copyable the loop in the existing specialization will be optimized to use std::memset.


Another reason you do not have to add a specialization or the overload to std is ADL. For that I refer you to this answer.


To your question about choosing the overload...

Here:

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

foo_t<T,N>::iterator is a non deduced context. You cannot infer the type of container from the type of iterator. For example std::vector<int>::iterator can be implemented as plain int*. However, the same is true for std::array<N,int>::iterator. Or more specifically, there is no reason to expect std::array<N,int>::iterator to be different for different N.


If you want to write a function that does something special for iterators to trivially copyable types you can use std::is_trivially_copyable to write a concept

#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);
}

Output:

trivially copybale
genereal overload

Forgive me the verbose unreadable concept. I suppose it could be written much shorter, it just checks if dereferencing an instance of type T yields a type that can be copied via std::memcpy or std::memset.

Upvotes: 1

Jarod42
Jarod42

Reputation: 217850

with typename foo_t<T, N>::iterator, T and N are non deducible.

friend function helps:

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; }

Demo

Upvotes: 2

Related Questions