SFINAE/requires-friendly decomposition declarations aka structured bindings

186 views
Skip to first unread message

lucdanton

unread,
Jun 28, 2016, 1:24:40 PM6/28/16
to SG8 - Concepts
Wording for structured bindings[1] was voted into the C++ draft at the 2016 Oulu meeting. Structured bindings make it possible to write the following code:

struct custom_triple {
   
int width;
   
int length;
   
int age_of_the_captain;
};

custom_triple triple
= { 3, 5, 7 };

// this is called a decomposition declaration
auto& [x, y, z] = triple;

assert( &x == &triple.width && &y == &triple.length && &z == &triple.age_of_the_captain );

I've noticed with interest that as best as I can tell, it is not possible to express e.g. 'this type must be decomposable into a triple'. That is, it is not possible to put precise constraints on the following generic code:

template<typename Triple>
void generic(Triple triple)
{
   
auto& [x, y, z] = triple;
}

The big, major hurdle is that decomposition declarations are only allowed at block-scope and in for-range declarations. If they were at least allowed in parameter declarations then perhaps (together with a helpful SFINAE-friendly spec) the following could be doing the right thing:

void triple_check(auto& [x, y, z]);

template<typename Tri>
concept bool Triple = requires(Tri tri) { triple_check(tri); };

Or perhaps if they were allowed in parameter declarations and requires parameters were allowed to have defaults (which they aren't currently) we could do this:

template<typename Tri>
concept bool Triple = requires(Tri tri, auto& [x, y, z] = tri) { void(); };

Or perhaps if we could use declaration statements directly as constraints we could do this:

template<typename Tri>
concept bool Triple = requires(Tri tri) {
   
// must be well-formed for requires expression to be true
   
auto& [x, y, z] = tri;
};

This is an interesting interaction of two tentative features of the language, so if anything needs changing and/or additional wording then I'm not sure which it is.

  [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0217r2.html

Casey Carter

unread,
Jun 28, 2016, 2:17:52 PM6/28/16
to SG8 - Concepts

On Tuesday, June 28, 2016 at 10:24:40 AM UTC-7, lucdanton wrote:
Wording for structured bindings[1] was voted into the C++ draft at the 2016 Oulu meeting. Structured bindings make it possible to write the following code:

struct custom_triple {
   
int width;
   
int length;
   
int age_of_the_captain;
};

custom_triple triple
= { 3, 5, 7 };

// this is called a decomposition declaration
auto& [x, y, z] = triple;

assert( &x == &triple.width && &y == &triple.length && &z == &triple.age_of_the_captain );

I've noticed with interest that as best as I can tell, it is not possible to express e.g. 'this type must be decomposable into a triple'. That is, it is not possible to put precise constraints on the following generic code:

template<typename Triple>
void generic(Triple triple)
{
   
auto& [x, y, z] = triple;
}

The big, major hurdle is that decomposition declarations are only allowed at block-scope and in for-range declarations.

We can lower the syntax of the concept requirements into the definition of the syntax for structured decomposition.  Namely, an expression e of type E is N-decomposable if either (a) E is an array type with extent N, or (b) std::tuple_size<E>::value == N, and (there is a declaration of e.get and e.get<i>() is a valid expression for all i in [0,N)) or (e.get finds no declarations and get<i>(e) (found by ADL) is a valid expression for all i in [0,N)). As a first attempt, I'd conceptify that as:

namespace detail {
    template <std::size_t> void get();
    namespace adl_get {
        template<std::size_t I, class T>
        constexpr auto impl(T&& t)
            noexcept(noexcept(get<I>(std::forward<T>(t)))) ->
            decltype(get<I>(std::forward<T>(t)))
        { return get<I>(std::forward<T>(t)); }
    }
}

template<class T, std::size_t N>
concept bool Decomposable =
    std::extent<T>::value == N ||
    std::tuple_size<T>::value == N && (
        requires(T&& t, std::size_t i) {
            t.get<i>();
        } || requires(T&& t, std::size_t i) {
            detail::adl_get::impl<i>(t);
        }
    );

void generic(Decomposable<3>& triple)
{
    
auto& [x, y, z] = triple;
}

Although given the deleterious effects of disjunctions in concepts on compile performance, I'd be tempted to hide the entire mess in a predicate constraint for any serious use.

Casey Carter

unread,
Jun 28, 2016, 2:20:10 PM6/28/16
to SG8 - Concepts
woops - both uses of t need to be forwarded here:
 
template<class T, std::size_t N>
concept bool Decomposable =
    std::extent<T>::value == N ||
    std::tuple_size<T>::value == N && (
        requires(T&& t, std::size_t i) {
            std::forward<T>(t).get<i>();
        } || requires(T&& t, std::size_t i) {
            detail::adl_get::impl<i>(std::forward<T>(t));
        }
    );

Andrew Sutton

unread,
Jun 28, 2016, 2:28:12 PM6/28/16
to SG8 - Concepts
Was just thinking through this when I saw the post, but I called it Tuple<T, N> instead of Decomposable.



--
You received this message because you are subscribed to the Google Groups "SG8 - Concepts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concepts+u...@isocpp.org.
To post to this group, send email to conc...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/concepts/.
--
Andrew Sutton

Casey Carter

unread,
Jun 28, 2016, 2:30:43 PM6/28/16
to SG8 - Concepts

On Tuesday, June 28, 2016 at 11:20:10 AM UTC-7, Casey Carter wrote:
Stuff

All of which is wonderful in the first two cases, but omits the third case: decomposition of a non-array, non-tuple-like type. (Fingers racing ahead of my eyes.) Yes, I'll have to admit there's no concepts syntax that will work for the final case. 

Andrew Sutton

unread,
Jun 28, 2016, 3:14:51 PM6/28/16
to SG8 - Concepts
You mean something like a plain struct?

struct foo {
  string a;
  int b;
};

That would be great, but we'll need language support (default generation of get<N>, tuple_xxx, anybody?). 



--
You received this message because you are subscribed to the Google Groups "SG8 - Concepts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concepts+u...@isocpp.org.
To post to this group, send email to conc...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/concepts/.
--
Andrew Sutton

Casey Carter

unread,
Jun 28, 2016, 3:30:06 PM6/28/16
to SG8 - Concepts
On Tuesday, June 28, 2016 at 12:14:51 PM UTC-7, Andrew Sutton wrote:
You mean something like a plain struct?

struct foo {
  string a;
  int b;
};

That would be great, but we'll need language support (default generation of get<N>, tuple_xxx, anybody?). 


Take another look at P0217R3:

Otherwise, all of E's non-static data members shall be public direct members of E or of the same unambiguous public base class of E, E shall not have an anonymous union member, and the number of elements in the identifier-list shall be equal to the number of non-static data members of E. The i-th non-static data member of E in declaration order is designated by mi. Each vi is the name of an lvalue that refers to the member mi of e and whose type is cv Ti, where Ti is the declared type of that member; the referenced type is cv Ti. The lvalue is a bit-field if that member is a bit-field.

Andrew Sutton

unread,
Jun 28, 2016, 3:44:35 PM6/28/16
to SG8 - Concepts
Right, we can get decomposition for (many) classes, but they don't otherwise behave as tuples. Unless I'm being dense and missing your point entirely.



--
You received this message because you are subscribed to the Google Groups "SG8 - Concepts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concepts+u...@isocpp.org.
To post to this group, send email to conc...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/concepts/.
--
Andrew Sutton

Casey Carter

unread,
Jun 28, 2016, 3:57:50 PM6/28/16
to SG8 - Concepts
On Tuesday, June 28, 2016 at 12:44:35 PM UTC-7, Andrew Sutton wrote:
Right, we can get decomposition for (many) classes, but they don't otherwise behave as tuples. Unless I'm being dense and missing your point entirely.


To be clear: Yes, I'm pointing out that in addition to array and tuple-like types structured decomposition is applicable to some simple class types. We can write concepts to constrain types to the first two cases, but not the third. You once asked me if I thought that syntax requiring a variable declaration to be valid would be useful for the concepts defined in the Ranges TS. There were a few things it would simplify slightly. Structured decomposition provides a stronger motivation: it's quite a complicated initialization with no equivalent expression syntax.
Reply all
Reply to author
Forward
0 new messages