How to write a concept for structured bindings? - c++

While compiling the following (reduced) code:
#include <tuple>
#include <stdlib.h>
template<size_t N> struct tying;
template<> struct tying<1> {static auto function(auto& o) -> decltype(auto) {auto& [p1] = o;return std::tie(p1);}};
template<> struct tying<2> {static auto function(auto& o) -> decltype(auto) {auto& [p1,p2] = o;return std::tie(p1,p2);}};
template<typename T, size_t N> concept bool n_components =
requires(T& object) {
{ tying<N>::function(object) };
};
typedef struct
{
int a;
float b;
} test_t;
int main(int argc, char* argv[])
{
constexpr size_t n = 1;
constexpr bool t = n_components<test_t, n>;
printf("n_components<test_t, %d>: %s\n", n, t ? "yes" : "nope");
return 0;
}
with gcc specific options -std=c++1z -fconcepts the gcc (version 7.3.0, and x86-64 gcc-trunk at godbolt also) fails with an error:
error: only 1 name provided for structured bindingnote: while 'test_t' decomposes into 2 elements
I was very surprised by this, as the error "raises" inside the requires-expression, and to my understanding, this should result in n_components constraint to get evaluated to false, as the requirement is not satisfied.
Note: replacing constexpr size_t n = 1; with other values "fixes" the error, so I presume the n_components constraint is not to blame:
replacing with n = 2 the n_components constraint evaluates to "true" as expected.
replacing with n = 3 the n_components constraint evaluates to "false" as expected - due to referring to unspecialized tying<3> struct.
It seems there are no other compilers supporting the c++ concepts and structured bindings available yet to compare with.
PS. I was playing around with "poor man's reflection" a.k.a. magic_get and hoped to replace the unreliable is_braces_constructible trait with something more robust...

Here's a partial solution for clang since 5.0.
It is distilled from a recent reddit post
Find the number of structured bindings for any struct
The method is non-standard, using gnu extension statement expressions to effectively SFINAE on structured binding declarations. It is also non-portable as gcc fails to parse structured binding in statement expression in a lambda trailing return type.
template <typename... Ts> struct overloads : Ts... { using Ts::operator()...; };
template <typename... Ts> overloads(Ts...) -> overloads<Ts...>;
template <typename T>
auto num_bindings_impl() noexcept {
return overloads{
[](auto&& u, int) -> decltype(({auto&& [x0] = u; char{};}))(*)[1] {return {};},
[](auto&& u, int) -> decltype(({auto&& [x0,x1] = u; char{};}))(*)[2] {return {};},
[](auto&& u, int) -> decltype(({auto&& [x0,x1,x2] = u; char{};}))(*)[3] {return {};},
[](auto&& u, int) -> decltype(({auto&& [x0,x1,x2,x3] = u; char{};}))(*)[4] {return {};},
[](auto&& u, unsigned) -> void {}
}(declval<T>(), int{});
};
This implementation to be used only in unevaluated context and expanding out
for as many bindings as you wish, up to compiler implementation limits.
Then, you can define traits that work up to that limit:
template <typename T>
inline constexpr bool has_bindings = [] {
if constexpr ( ! is_empty_v<T> )
return ! is_void_v< decltype(num_bindings_impl<T>()) >;
else return false;
}();
template <typename T>
inline constexpr unsigned num_members = [] {
if constexpr ( ! is_empty_v<T> )
return sizeof *num_bindings_impl<T>();
else return 0;
}();
https://godbolt.org/z/EVnbqj

The immediate situation
I was very surprised by this, as the error "raises" inside the requires-expression
To be precise, the error happens in the body of the functions you wrote. We can boil down your code to:
void function(auto& arg);
void function_with_body(auto& arg)
{
arg.inexistent_member;
}
template<typename Arg>
concept bool test = requires(Arg arg) { function(arg); };
// never fires
static_assert( test<int> );
template<typename Arg>
concept bool test_with_body = requires(Arg arg) { function_with_body(arg); };
// never fires
static_assert( test_with_body<int> );
(on Coliru)
As far as the requires expression are concerned, the function calls are valid C++ expressions: there's nothing tricky about their return and parameter types, and the supplied argument can be passed just fine. No check is performed for the function bodies, and with good reason: the function or function template may have been declared but not defined yet (i.e. as is the case for function). Instead, it's normally up to the function template writer to make sure that the function body will be error-free for all (sensible) specializations.
Your code uses return type deduction, but that won't make too much of a difference: you can declare a function with a placeholder type (e.g. decltype(auto)) without defining it, too. (Although a call to such a function is in fact an invalid expression since the type cannot be deduced and that's visible to a requires expression, but that's not what you're doing.) In pre-concepts parlance, return type deduction is not SFINAE-friendly.
Structured bindings
As to your wider problem of writing a constraints around structured bindings, you are out of luck. As best as I know these are all the obstacles:
There is no interface for querying the decomposition size of a type, even though the implementation blatantly has access to the information in order to type-check structured binding declarations. (std::tuple_size is for tuple-like types only.). This could be worked around by blindingly trying sizes in increasing order though.
You can only ever write constraints around types and expressions, but structured bindings can only appear in normal variable declarations. (If function parameters could be structured bindings we could work around that.) No trickery with lambda expressions allowed because these can't appear in an unevaluated operand, such as in the body of a requires expression.
The closest you can go is emulate what the language is doing during an actual structured binding. That can get you support for tuple-like types (including arrays), but not 'dumb' aggregates.
(range-for is another case of a statement-level constraint that you can't fit into an expression constraint, and where emulating the language can only get you so far.)

Related

Unable to use std::apply on user-defined types

While implementing a compressed_tuple class for some project I'm working on, I ran into the following issue: I can't seem to pass instances of this type to std::apply, even though this should be possible according to: https://en.cppreference.com/w/cpp/utility/apply.
I managed to reproduce the issue quite easily, using the following fragment (godbolt):
#include <tuple>
struct Foo {
public:
explicit Foo(int a) : a{ a } {}
auto &get_a() const { return a; }
auto &get_a() { return a; }
private:
int a;
};
namespace std {
template<>
struct tuple_size<Foo> {
constexpr static auto value = 1;
};
template<>
struct tuple_element<0, Foo> {
using type = int;
};
template<size_t I>
constexpr auto get(Foo &t) -> int & {
return t.get_a();
}
template<size_t I>
constexpr auto get(const Foo &t) -> const int & {
return t.get_a();
}
template<size_t I>
constexpr auto get(Foo &&t) -> int && {
return std::move(t.get_a());
}
template<size_t I>
constexpr auto get(const Foo &&t) -> const int && {
return move(t.get_a());
}
} // namespace std
auto foo = Foo{ 1 };
auto f = [](int) { return 2; };
auto result = std::apply(f, foo);
When I try to compile this piece of code, it seems that it cannot find the std::get overloads that I have defined, even though they should perfectly match. Instead, it tries to match all of the other overloads (std::get(pair<T, U>), std::get(array<...>), etc.), while not even mentioning my overloads. I get consistent errors in all three major compilers (MSVC, Clang, GCC).
So my question is whether this is expected behavior and it's simply not possible to use std::apply with user-defined types? And is there a work-around?
So my question is whether this is expected behavior and it's simply
not possible to use std::apply with user-defined types?
No, there is currently no way.
In libstdc++, libc++, and MSVC-STL implementations, std::apply uses std::get internally instead of unqualified get, since users are prohibited from defining get under namespace std, it is impossible to apply std::apply to user-defined types.
You may ask, in [tuple.creation], the standard describes tuple_cat as follows:
[Note 1: An implementation can support additional types in the
template parameter pack Tuples that support the tuple-like protocol,
such as pair and array. — end note]
Does this indicate that other tuple utility functions such as std::apply should support user-defined tuple-like types?
Note that in particular, the term "tuple-like" has no concrete definition at
this point of time. This was intentionally left C++ committee to make this gap
being filled by a future proposal. There exists a proposal that is
going to start improving this matter, see P2165R3.
And is there a work-around?
Before P2165 is adopted, unfortunately, you may have to implement your own apply and use non-qualified get.
The question has throughly been answered by #康桓瑋. I'm going to post some more details on how to provide a workaround.
First, here is a generic C++20 apply function:
#include<tuple>
#include<functional>
namespace my
{
constexpr decltype(auto) apply(auto&& function, auto&& tuple)
{
return []<size_t ... I>(auto && function, auto && tuple, std::index_sequence<I...>)
{
using std::get;
return std::invoke(std::forward<decltype(function)>(function)
, get<I>(std::forward<decltype(tuple)>(tuple)) ...);
}(std::forward<decltype(function)>(function)
, std::forward<decltype(tuple)>(tuple)
, std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<decltype(tuple)> > >{});
}
} //namespace my
The own namespace is useful so that the custom apply does not interfere with the std-version. The unqualified call to get means (quoting #Quuxplusone from his blog, which gives the best explanation I encountered so far):
An unqualified call using the two-step, like using my::xyzzy;
xyzzy(t), indicates, “I know one way to xyzzy whatever this thing may
be, but T itself might know a better way. If T has an opinion, you
should trust T over me.”
You can then roll out your own tuple-like class,
struct my_tuple
{
std::tuple<int,int> t;
};
template<size_t I>
auto get(my_tuple t)
{
return std::get<I>(t.t);
}
namespace std
{
template<>
struct tuple_size<my_tuple>
{
static constexpr size_t value = 2;
};
}
With the overload of get() and the specialization of std::tuple_size, the apply function then works as expected. Moreover, you can plug in any compliant std-type:
int main()
{
auto test = [](auto ... x) { return 1; };
my::apply(test, my_tuple{});
my::apply(test, std::tuple<int,double>{});
my::apply(test, std::pair<int,double>{});
my::apply(test, std::array<std::string,10>{});
}
DEMO

Is there anyway to get a lambda's return value by deduction without passing the argument types?

Consider the following example:
template<auto const fnc>
struct dummy_s {
typedef std::invoke_result<decltype(fnc), std::uint8_t >::type return_t;
};
int main() {
dummy_s<[](std::uint8_t const& n) -> bool { return true ^ n; }>::return_t s = true;
}
Is there anyway to get the return type without specifying std::uint8_t or whatever the number of arguments is, as template parameter as example.
You could write a metafunction that gives you the type of the first argument
template<typename Ret, typename Arg>
auto arg(Ret(*)(Arg)) -> Arg;
and then decay the lambda fnc to a function pointer (using + say), that you pass to arg, and then use that in the typedef.
typedef std::invoke_result<decltype(fnc),
decltype(arg(+fnc))>::type return_t;
This will only work for lambdas that don't capture anything, and that take a single argument.
You can also considerably simplify the typedef inside the struct by simply using arg directly like this
using return_t = decltype(arg(+fnc)); // using is cleaner than a typedef as well
This avoids using invoke_result entirely, and lets you define arg in a way that allows lambdas with multiple arguments to be passed to it
template<typename Ret, typename Arg, typename ...R>
auto arg(Ret(*)(Arg, R...)) -> Arg;
Here's a demo
As I said in my comment, each time I though I needed this feature I realized later I was going the wrong path. The reason is that as soon as the lambda is a template (auto) there is no hope to make it work.
More generally, if you don't have clue of the "universe" inputs of a function you don't really have a function, conceptually speaking.
If you think you still need it, you can use Boost.TypeTraits, lambda decay and function pointers.
#include<cstdint>
#include<boost/type_traits.hpp>
int main(){
auto f = [](std::uint8_t const& n) -> bool {return true ^ n;};
using f_return = boost::function_traits<decltype(*+f)>::result_type;
static_assert( std::is_same<f_return, bool>{} , "!");
}
With any generalization of f, overload or templates, will not work.
You are really lucky that this sort of works because of a series of quirks in the language starting from the existence of monomorphic functions (inherited from C, pointer decay, etc). Conceptually, it is horrible.
Having said that, there is also a potential problem that is very sensitive to the version of C++ you are using, that is the use of lambdas in template (non-evaluated) contexts.
This is working solution based on your code.
It really works as is with certain combinations of compilers and flags, for example https://godbolt.org/z/5x684nfWc :
#include<cstdint>
#include<boost/type_traits.hpp>
template<auto fnc>
struct dummy_s {
using return_t = typename boost::function_traits<decltype(*+fnc)>::result_type;
};
int main() {
typename dummy_s<[](std::uint8_t const& n) -> bool { return true ^ n; }>::return_t s = true;
}

Checking for constexpr in a concept

I just started doing concepts. The syntax for checking expressions is really useful and removes me a lot of boilerplate I had with sfinae. However I wondered how can I check if an expression can be used in a constexpr context. Sometimes those expression results in void. The way I can imagine would look like this, with the constexpr keyword uncommented:
template<typename T>
concept foo = requires(T t) {
/* constexpr */ { t.bar(); } -> std::same_as<void>;
/* constepxr */ { T::foo; };
}
However, I highly doubt this is the right syntax. Is there an easy way to check for the constexpr-ness of the expression in a concept?
I don't want to check if the evaluation of a constexpr function won't fail for all possible values of t, I want to know if I can put that expression at a place where the compiler expect something to be evaluable at compile time.
I think the expected concept can be created using std::bool_constant, which has the property that the failure of substitution in its argument of not-constant expression is not a compilation error, but just makes the concept false.
The proposed solution is
#include <concepts>
#include <type_traits>
template<typename T>
concept HasConstexprVoidBar =
requires(T t) {
{ t.bar() } -> std::same_as<void>;
{ std::bool_constant<(T{}.bar(), true)>() } -> std::same_as<std::true_type>;
};
struct A {
constexpr void bar() {}
};
struct B {
void bar() {}
};
int main() {
// concept check passes for constexpt A::bar()
static_assert( HasConstexprVoidBar<A> );
// concept check fails for B::bar()
static_assert( !HasConstexprVoidBar<B> );
}
Here the concept HasConstexprVoidBar is verified successfully for struct A having constexpr void method bar and evaluates to false for struct B having not-constexpr method.
Demo: https://gcc.godbolt.org/z/nsx9z99G4
How about this one:
template <typename F, auto Test=std::bool_constant<(F{}(), true)>()>
consteval auto is_constexpr (F) { return Test; }
EDIT: the one I wrote before doesn't work in clang > 13.0 for some reason, but this does
and then
requires (T t) {
is_constexpr([]{ T{}.cols(); });
}
Here we use the C++20's consteval func to force constexpr check inside and the fact that since C++20 simple lambdas can be default-constructed, hence we construct it here from F{} and here we go :)

C++ Concepts - Concept in the requires parenthesis causes 2 conflicting error messages

I've worked hard this morning to understand concepts a bit more (still a newbie), and I've stumbled to something strange.
I wanted to understand how good concepts work with Lambda functions, I've tried toying around with them a lot.
Lets start with this code (that works!):
#include <concepts>
#include <stdio.h>
template <typename T>
concept Integral =
requires(T n)
{
{ n } -> std::convertible_to<int>;
};
template <typename Operation>
concept CalculatorOperation =
requires(Operation&& op, int a, int b)
{
{ op(a, b) } -> Integral;
};
template <CalculatorOperation Operation>
static inline constexpr Integral auto Calculator(const Integral auto First,
const Integral auto Second)
{
return Operation{}(First, Second);
}
int main()
{
static constexpr auto Multiply = [](const Integral auto First,
const Integral auto Second)
{ return First * Second; };
return Calculator<decltype(Multiply)>(15, 18);
}
I created an Integral concept that (just for dummy's sake) is satisfied if a type is convertible to int.
I've created a calculator operation concept that accepts a lambda, two ints, and makes sure the operation returns an Integral.
I decided that I want the int in the concept to turn into an Integral, since I don't want only int's to be accepted to the operation (I want all integral types).
So I changed
requires(Operation&& op, int a, int b)
to
requires(Operation&& op, Integral&& a, Integral&& b)
Not only did this not work for me, I actually got 2 conflicting error messages (on GCC 10.1):
<source>:13:30: error: placeholder type not allowed in this context
<source>:13:30: error: expected 'auto' or 'decltype(auto)' after 'Integral'
13 | requires(Operation&& op, Integral&& a, Integral&& b)
I ran this using GodBolt's website Compiler Explorer.
Whats going on here? Is there an actual way to do this?
First off, by doing this:
requires(Operation&& op, Integral&& a, Integral&& b)
you probably actually meant this:
requires(Operation op, Integral auto a, Integral auto b)
that is, to use the type-constraint auto form of a placeholder type specifier.
However, auto parameters produce function templates, and the auto placeholder is an equivalent of an actual invented type template parameter of that function template, thus it can appear only in contexts where type deduction from an initializer takes place, and that means that:
void foo(auto x);
is an equivalent of:
template <typename T>
void foo(T x);
Similarly,
void bar(Integral auto x);
is an equivalent of:
template <Integral T>
void bar(T x);
Such transformation is not applicable to a requires expression, i.e., there's no initializer whose type could be deduced, and so one cannot use an auto placeholder in the parameter declaration of a requirement parameter list. These parameters, as the standard says, "are only used as notation for the purpose of defining requirements." So instead, you need to explicitly specify all template arguments that are needed for the concept to work, e.g.:
template <typename Operation, typename Lhs, typename Rhs>
concept CalculatorOperation = requires(Operation op, Lhs a, Rhs b)
{
{ op(a, b) } -> Integral;
};
template <typename Operation>
constexpr Integral auto Calculator(Integral auto first, Integral auto second)
requires CalculatorOperation<Operation, decltype(first), decltype(second)>
{
return Operation{}(first, second);
}
Also note that I removed && from each of the parameter declarations in the requirement parameter list. The whole requires expression is an unevaluated context, and no arguments are actually passed.

Failing to work around g++ 7.1 structured binding bug with Boost.Bimap

In my project I am using Boost.Bimap to implement bidirectional maps.
Look at this very simple MCVE on godbolt, where I am using structured binding to print the key-value pair of the right map (which, according to the documentation, is signature-compatible to std::map.
Problem
It compiles fine for any g++ version >= 7.4 and later, however I need to use g++ 7.1. and here this code fails with the following message:
<source>: In function 'int main()':
<source>:11:20: error: 'std::tuple_size<const boost::bimaps::relation::structured_pair<boost::bimaps::tags::tagged<const long unsigned int, boost::bimaps::relation::member_at::right>, boost::bimaps::tags::tagged<const std::__cxx11::basic_string<char>, boost::bimaps::relation::member_at::left>, mpl_::na, boost::bimaps::relation::mirror_layout>>::value' is not an integral constant expression
for (const auto& [key, value] : bm.right) {
I was able to find out that this is due to a bug in g++ that seems to have been fixed in later versions.
Workaround attempt (toy example, successful)
In order to make the structured bindings work with my compiler version, I attempted to create a workaround by specializing std::tuple_size, std::tuple_element and std::get. See this cppreference link for more information.
For simplicity, I successfully tried this first with a toy structure. Here are the specializations, check out the full code on godbolt.org:
struct SampleType {
int a = 42;
std::string b = "foo"s;
double c = 3.141;
};
#if (__GNUC__ == 7) && (__GNUC_MINOR__ == 1)
template <std::size_t N>
decltype(auto) get(const ::SampleType& t) {
if constexpr (N==0) return t.a;
else if constexpr (N==1) return t.b;
else return t.c;
}
namespace std {
// Tuple size is 3
template <> struct tuple_size<::SampleType> : std::integral_constant<std::size_t, 3> {};
// Define tuple types
template <std::size_t N> struct tuple_element<N, ::SampleType> {
// Deduce type from get() function template defined above
using type = decltype(::get<N>(std::declval<::SampleType>()));
};
}
#endif
Note that if you remove the #ifdef for g++ 7.1., the compilation will fail with the same error as above (...is not an integral constant expression). (Interesting: Unlike the boost::bimap example, which only compiles fine with g++ 7.4 onwards, the toy example already succeeds with g++ 7.2)
Workaround attempt (original example, not successful)
Now, being very convinced that I found the solution, I made an attempt to do the same for boost::bimap but I am failing helplessly (check it out on godbolt.org):
template <std::size_t N>
decltype(auto) get(const bimap::right_map::value_type& bm) {
if constexpr (N==0) return bm.first;
else if constexpr (N==1) return bm.second;
}
namespace std {
// Tuple size is 2 -> key-value pair
template <> struct tuple_size<bimap::right_map::value_type> : std::integral_constant<std::size_t, 2> {};
// Define tuple types
template <> struct tuple_element<0, bimap::right_map::value_type> { using type = std::string; };
template <> struct tuple_element<1, bimap::right_map::value_type> { using type = std::size_t; };
}
The error message is too long to post here (see godbolt output), but basically I understand that the overload for "my" get is not being matched by the compiler. Note that for debugging reasons I have inserted the following line into my code to make sure that I am actually dealing with the correct type in my specializations.
for (const auto& pair : bm.right) {
// Make sure we capture the right type in the specializations above
static_assert(std::is_same<
decltype(pair),
const bimap::right_map::value_type&
>::value);
}
Am I doing something wrong? Or is this bug posing an insurmountable obstactle to my workaround attempt?
I don't think this is something you can work around.
Here's a shorter reproduction:
#include <tuple>
namespace N {
struct X {
template <typename T> void get() { }
};
}
namespace std {
template <> struct tuple_size<N::X> : integral_constant<size_t, 1> { };
template <> struct tuple_element<0, N::X> { using type = int; };
}
namespace N {
template <size_t I> decltype(auto) get(X const&) { return 42; }
}
int main() {
auto [i] = N::X{};
}
This is a valid program. The wording from [dcl.struct.bind]/4 says, emphasis mine:
The unqualified-id get is looked up in the scope of E by class member access lookup ([basic.lookup.classref]), and if that finds at least one declaration that is a function template whose first template parameter is a non-type parameter, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e), where get is looked up in the associated namespaces ([basic.lookup.argdep]).
The fact that N::X has a member function template get() that takes a type template parameter should cause us to then consider ADL lookup on get, which should find the non-member N::get. gcc 7.4 does this correctly, gcc 7.3 complains about N::X::get() not working.
The only way to work around that is to wrap the initializer somehow. Basically do something like:
auto [i] = wrap(N::X{});
Where wrap returns some new type that definitely doesn't have a member named get, so that you can provide the non-member you want. I'm not sure if there's a solution here that doesn't require additional wrapping. Besides just using gcc 7.4 :-)