Is it valid to use aliased containers in `ranges::to`?
Asked Answered
S

1

7

Currently only MSVC supports the nifty helper ranges::to, so I am unable to validate this in another compiler. Basically I have a type alias for a STL container and as soon as I attempt to pass it to ranges::to, compilation fails. So is this a valid usage? Why does the second (commented-out) example below not compile?

#include <ranges>
#include <vector>
#include <iostream>

template <typename T>
using Vec = std::vector<T>;

int main(int argc, char* argv[]) {
    auto vec = std::views::iota(1, 10) | std::ranges::to<std::vector>();
    //auto vec = std::views::iota(1, 10) | std::ranges::to<Vec>(); // C2440: cannot convert from 'void' to 'std::vector'.

    for (auto& v : vec)
        std::cout << v << ", ";

    std::cout << std::endl;
}
Silvanus answered 19/4, 2023 at 12:22 Comment(4)
For what it is worth, I used range-v3 and C++17 and (after changing to the range namespace) both vector productions worked for me using clang++ on Unix. I do recommend giving range-v3 a try with MSVC. The range-v3 is the origin of ranges support in C++20 .Valediction
std::vector has more than 1 template parameter. The alias Vec should be template <typename ...Ts> using Vec = std::vector<Ts...>; and that should work.Zambia
@Zambia Yes, that works. If your only problem was that you don't have access to a current MSVC compiler, then trust my word and post the answer. :)Supercharger
@cigien: Right, that works, thanks! But I'm not quite sure why omitting it does not work, since the second type is defaulted to std::allocator<T>. So I suspected this should also work: using Vec = std::vector<T, std::allocator<T>>; and also tried this earlier. However, it doesn't. Does ranges::to need to pass the allocator?Silvanus
C
3

Here's a reduced example, dropping ranges stuff

#include <type_traits>

template <typename T, typename Alloc=T>
struct A {
    template <typename R>
    A(R&&, Alloc={});
};

template <typename R, typename Alloc=std::type_identity_t<R>>
A(R&&, Alloc={}) -> A<std::type_identity_t<R>, Alloc>;

#if 0
    template <typename T> using B = A<T>;
#elif 0
    template <typename T, typename U=T> using B = A<T, U>;
#elif 0
    template <typename... Ts> using B = A<Ts...>;
#endif

int main(int argc, char* argv[]) {
    auto a = A(1);
    auto b = B(1);
}

There are three different approaches to implementing B, which should probably behave the same for this example, but don't:

  • everybody rejects the unary alias
  • gcc accepts the binary alias, but only if U is defaulted
  • gcc and msvc accept the variadic alias

I don't really know who's right, the rules for class template argument deduction for alias ([over.match.class.deduct], starting at /3) are pretty involved. It sure seems like the three definitions should give you the same result here, but...


Either way, it is definitely valid to use alias templates with ranges::to. It's just a matter of what the actual rules for deduction are from alias templates and to what extent they actually work.

Condition answered 19/4, 2023 at 13:33 Comment(1)
Thanks, this answers my question, although it's a little bit unsatisfactory. :-D I guess I stick to ranges::to<Vec<int>> then, since this deducts the actual type. :-)Silvanus

© 2022 - 2024 — McMap. All rights reserved.