Search code examples
c++templatesc++20metaprogramming

Deducting template parameter for iterator constructor


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?

Godbolt link

Example class

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


Solution

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