I am trying to create a constructor for my class which takes iterators of containers as arguments and creates another type of container (suppose something like a span from an array).
I would like to use a template deduction guide to support this and I was successful for non-cv qualified iterators, but it failed to deduct when I passed const-iterators:
// Deduction guide
<template typename Iterator>
Foo(Iterator begin, Iterator end) -> Foo<typename std::iterator_traits<Iterator>::value_type>;
std::vector<int> v{1, 2, 3, 4, 5};
Foo foo(v.begin(), v.end());
Foo otherFoo(v.cbegin(), v.cend()); // fails deduction
With a bit of trial and error, I was able to make it work with this deduction guide:
template <class Iterator>
Foo(Iterator begin, Iterator end) -> Foo<std::conditional_t<std::is_const_v<typename std::iterator_traits<Iterator>::value_type>,
typename std::iterator_traits<Iterator>::value_type,
typename std::iterator_traits<Iterator>::value_type const>>;
But as I am compiling with C++20, I don't understand why this works. From cppreference, std::iterator_traits::value_type
should be stripped of cv-qualification.
As a test, using the same environment I made the following test:
// test with non-const iterator
if (std::is_const_v<std::iterator_traits<decltype(v.begin())>::value_type>)
{
std::cout << "it is const" << std::endl;
} else
{
std::cout << "It is not const" << std::endl;
}
// test with const iterator
if (std::is_const_v<std::iterator_traits<decltype(v.cbegin())>::value_type>)
{
std::cout << "it is const" << std::endl;
} else
{
std::cout << "It is not const" << std::endl;
}
And got the expected: both are printing that "It is not const". So why would the template guide above work?
template <class T>
class Foo{
public:
template <class Iterator>
Foo(Iterator begin, Iterator end)
: mData(&*begin)
, mSize(std::distance(begin, end)) {}
T operator[](std::size_t i) {
return *(mData+i);
}
private:
T *mData;
std::size_t mSize;
};
// Template deduction guides
template <class Iterator>
Foo(Iterator begin, Iterator end) -> Foo<std::conditional_t<std::is_const_v<typename std::iterator_traits<Iterator>::value_type>,
typename std::iterator_traits<Iterator>::value_type,
typename std::iterator_traits<Iterator>::value_type const>>;
So why would the template guide above work?
Since value_type
is always cv-unqualified, this makes std::conditional_t
always false, so your reduction guide is equivalent to
template <class Iterator>
Foo(Iterator begin, Iterator end) ->
Foo<typename std::iterator_traits<Iterator>::value_type const>;
which means that Foo(v.begin(), v.end())
is actually deduced to be Foo<const int>
instead of Foo<int>
.
The appropriate definition in C++20 would be
template <class Iterator>
Foo(Iterator begin, Iterator end) ->
Foo<std::remove_reference_t<std::iter_reference_t<Iterator>>>;