[libcxx-commits] [PATCH] D96523: [libc++] Rewrite the tuple constructors to be strictly Standards conforming

Zoe Carver via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Thu Feb 11 22:19:46 PST 2021


zoecarver added a comment.

What would you think about extending the `_v` type traits to always be available (no matter the standard version)? Then we could remove a lot of the `_And`s and similar types which would mean we would instantiate a lot few types, therefore, having much better compile time. Not a big deal, but it might be nice.



================
Comment at: libcxx/include/tuple:794
+    // tuple(const pair<U1, U2>&) constructors (including allocator_arg_t variants)
+    template <class _Up1, class _Up2, class _Dummy = void>
+    using _EnableCopyFromPair = _And<
----------------
Wait... why do we have `_Dummy` types //here//? Why can't this just be:
```
template <class U1, class U2>
static const size_t __enable_copy_from_pair = sizeof...(_Tp) == 2 && 
    is_constructible<_FirstType <_Tp...>, const U1&>::value &&
    is_constructible<_SecondType<_Tp...>, const U2&>::value;
```


================
Comment at: libcxx/include/tuple:462
+    // tuple() constructors (including allocator_arg_t variants)
+    template <class _Dep = true_type, _EnableIf<
+        _And<_Dep,
----------------
ldionne wrote:
> zoecarver wrote:
> > I assume the `_Dep` is just a "dummy type" for the `_EnableIf`? If so, maybe we could keep calling it `_Dummy`, because we use that name in a lot of places in the codebase to indicate that the type doesn't matter, or is just used for a following `_EnableIf`. Right now, when I look at this, I'm wondering if it was changed to add a "dependency" to this constructor (which I don't think is the case). 
> > 
> > Additionally, if you change this to say `class _T2 = _Tp` or `class _Dummy = _Tp` you can omit the `_And`, I think. (Note: here it doesn't really matter because you'd go back to using `__all` but below I think you can eliminate some extra template logic, which is good for both readability and compile time.)
> `_Dep` stands for `_Dependent`. IOW, I'm making the `_And<...>` instantiation dependent on a template argument to avoid it being evaluated eagerly. I can use `_Dummy`, but I went for the shortest thing that made sense since it's used to make `_FirstType<...>` and `_SecondType<...>` dependent below, where having a long name hurts readability. I think `_Dummy` is fine, though.
> 
> > Additionally, if you change this to say class _T2 = _Tp
> 
> I can't do that, because `_Tp` is a parameter pack. Otherwise, I would replace all these occurrences by something notionally equivalent to `<class ..._DependentTp = _Tp>`, and I could get rid of much of the complexity. Unless I've missed something major, that isn't valid C++ right now though.
I suppose `_Dep` makes sense now that I understand what it means. But I still think `_Dummy` is better because that's what we use everywhere else, so thanks for changing it. 

> I can't do that, because _Tp is a parameter pack.

Ah, yes, you're right. For some reason, I thought you could, but it looks like that's not a feature. 


================
Comment at: libcxx/include/tuple:479
+    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
+    explicit tuple()
+        _NOEXCEPT_(_And<is_nothrow_default_constructible<_Tp>...>::value)
----------------
ldionne wrote:
> zoecarver wrote:
> > When were these conditionally explicit overloads added? I think C++17. 
> > 
> > This is a bit annoying because in C++20 we get the `explicit(bool)` operator which would make this implementation about 50% smaller. 
> > 
> > It looks like that was added to clang in V10. I wonder if we could get away with a macro to do this. There would be a small overlap of users on the V6-V10 clang version using C++17 who might be affected. 
> > When were these conditionally explicit overloads added? I think C++17.
> 
> Hmm, I'm not 100% sure, but that sounds about right. Perhaps we've been supporting those in older Standards as an extension. While I generally dislike extensions, I think that not enabling those implicit/explicit constructors pre-C++17 would be a pretty big cost in complexity for little benefit, so I'd rather implement them even pre-C++17. Open to discussion, of course.
> 
> > This is a bit annoying because in C++20 we get the explicit(bool) operator
> 
> Yes, I agree, those are a huge pain to write without `explicit(bool)`, but I don't think there's a way around that TBH. To be fair, there is quite a bit of code duplication, but it's simple code. I was also planning on simplifying the calls to the `__base_` constructor in a follow-up patch to reduce the magnitude of the duplication. Let me know if you have a blocking comment or if you want me to take an action here.
> While I generally dislike extensions, I think that not enabling those implicit/explicit constructors pre-C++17 would be a pretty big cost in complexity for little benefit, so I'd rather implement them even pre-C++17.

I think if we removed the extension, we'd actually reduce the complexity significantly. Here's what I had in mind:
```
#if STD > 14 && CLANG >=10
#define _EXPLICIT_COND(...) explicit(...)
#else
#define _EXPLICIT_COND
#endif
```
Then we could just annotate all these overloads with `_EXPLICIT_COND(...)`. The problem is we'd be non-conforming for those using C++17 on clang V9 or lower, and this would potentially break people's code if they were on C++11 or 14. WDYT? Could we get away with it? 😉

This is a non-blocking comment. I just thought it might be a way to reduce a bunch of code (but it looks it probably won't work for now, but maybe we can come back to it in a few years).


================
Comment at: libcxx/include/tuple:585
+        _BoolConstant<sizeof...(_Tp) >= 1>,
+        _Not<_IsThisTuple<_Up...> >, // extension to allow mis-behaved user constructors
+        is_constructible<_Tp, _Up>...
----------------
Quuxplusone wrote:
> ldionne wrote:
> > zoecarver wrote:
> > > Nit: it might be simpler to just say `sizeof...(_Up) != 1 || is_same<_Up,  tuple>...` (or even `__is_same(_Up..., tuple)`). Then you could get rid of `_IsThisTuple`.
> > I agree, I'd like to get rid of `_IsThisTuple` too. I tried the following three alternatives:
> > 
> > ```
> > _Not<is_same<__uncvref_t<_Up>, tuple>...>
> > ```
> > 
> > This doesn't work because we're expanding a pack into `_Not`, which takes a single template parameter.
> > 
> > ```
> > _Not<is_same<__uncvref_t<_Up>..., tuple> >
> > ```
> > 
> > This doesn't work because we're expanding a pack into `is_same`, which takes two parameters exactly.
> > 
> > ```
> > _BoolConstant<!__is_same(__uncvref_t<_Up>..., tuple)>
> > ```
> > 
> > This doesn't work but I don't know why.
> > 
> > Let me know if you can think of another way to get rid of `_IsThisTuple`.
> > 
> FWIW, I don't understand what the "extension" is here, nor what "mis-behaved user constructors" we're worried about. But I haven't cared enough to remove that line and re-run the unit tests to find out.
[[ https://godbolt.org/z/4cKzj9 | This works for me: ]]
```
template<class ...U>
    static const size_t u_types_constraints =
        sizeof...(U) == sizeof...(T) &&
        sizeof...(U) >= 1 &&
        !is_same<Tuple, U...>::value;

template<class ...U, class = _EnableIf<u_types_constraints<U...> > >
    Tuple(U&&...) { }
```

---

> FWIW, I don't understand what the "extension" is here, nor what "mis-behaved user constructors" we're worried about. But I haven't cared enough to remove that line and re-run the unit tests to find out.

I also am curious about what this means. I thought it was just to ensure that we picked the right overload (so that this doesn't look like a move constructor when `_Up = tuple`).


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D96523/new/

https://reviews.llvm.org/D96523



More information about the libcxx-commits mailing list