I have a concept for normal binary operators
template<typename Op, typename T> concept is_binary_operation =
requires (const T& t1, const T& t2) // e.g. a+b
{
{Op()(t1,t2)}->std::convertible_to<T>;
};
and a concept for compound assignment operators
template<typename Op, typename T> concept is_operation_and_assign =
requires (T& t1, const T& t2) // e.g a += b;
{
{Op()(t1,t2)}->std::convertible_to<T>;
};
For compound assignment operators this works as expected:
template<typename T> struct op_and_assign
{
T& operator()(T& t1, const T& t2)
{
t1 += t2;
return t1;
}
};
This "is_operation_and_assign" but not "is_binary_operation"
std::cout << is_binary_operation<op_and_assign<double>, double> << " ";
std::cout << is_operation_and_assign<op_and_assign<double>, double> << std::endl;
prints "0 1". std::plus, however, satisfies both concepts:
std::cout << is_binary_operation<std::plus<double>, double> << " ";
std::cout << is_operation_and_assign<std::plus<double>, double> << std::endl;
prints "1 1".
How do I have to change the concept "is_operation_and_assign" so that I get the output "1 0", i.e. so that it will fulfilled by op_and_assign but not by std::plus?
To make more clear what I need: I have two versions of an algorithm, one using the compound assignment operator, one using the binary operator:
template<typename Op, typename T>
int f() requires is_operation_and_assign<Op, T>
{
return 0;
}
template<typename Op, typename T>
int f() requires is_binary_operation<Op, T>
{
return 1;
}
I can call the version for op_and_assign
f<op_and_assign<double>, double>();
but the version for std::plus
f<std::plus<double>, double>();
does not compile. (error: call to 'f' is ambiguous)
Update: in the meanwhile I found a workaround:
When I simply add && !is_binary_operation<Op, T> to the first f:
template<typename Op, typename T>
int f() requires (is_operation_and_assign<Op, T>
&& !is_binary_operation<Op, T>)
{
return 0;
}
template<typename Op, typename T>
int f() requires is_binary_operation<Op, T>
{
return 1;
}
then the second call is no longer ambiguous, i.e. both
f<op_and_assign<double>, double>();
f<std::plus<double>, double>();
compile (and choose the desired function).
It's important to clarify what actually your concept is checking, because it's not what you think it is.
This:
template<typename Op, typename T> concept is_operation_and_assign =
requires (T& t1, const T& t2) // e.g a += b;
{
{Op()(t1,t2)}->std::convertible_to<T>;
};
Checks that you can invoke Op()(t1, t2) with a T& and a T const& and that you get something that satisfies convertible_to<T>. When you provide:
template<typename T> struct op_and_assign
{
T& operator()(T& t1, const T& t2)
{
t1 += t2;
return t1;
}
};
as the first template parameter, what does that actually check? This is an unevaluated expression, we're checking to see if we can invoke op_and_assign<T>(). We're not evaluating the body of the call operator, we're just checking to see if it's a valid call. So it's no different than if we wrote:
template<typename T> struct op_and_assign
{
T& operator()(T& t1, const T& t2);
};
It's unevaluated, there's no body, so the only things that matter are constraints. Here, there are no constraints, so op_and_assign is always invokable as long as the arguments are convertible.
When you do this:
is_binary_operation<op_and_assign<double>, double>
You're effectively asking if you can convert the arguments appropriately. For is_binary_operation, you're providing two arguments of type double const& (from your requires expression) but op_and_assign<double> needs to take one double&. That's why this particular check doesn't work.
For how to fix it. op_and_assign which should probably look something like this:
struct op_and_assign
{
template <typename T, typename U>
auto operator()(T&& t, U&& u) const -> decltype(t += u);
};
Now we're actually checking if we can perform +=.
But that won't change that you can't assign to a double const&. You're getting the correct answer there, even if you weren't doing the check you intended to.
Related
The following fails to compile.
template <typename... U>
requires requires(U... x) { (std::to_string(x) && ...); }
auto to_string(const std::variant<U...>& value) {
return std::visit([](auto&& value) {
return std::to_string(std::forward<decltype(value)>(value));
}, value);
}
int main() {
std::variant<int, float> v = 42;
std::cout << to_string(v) << std::endl;
}
https://godbolt.org/z/Wvn6E3PG5
If I convert the direct requires expression into a concept though, it works fine.
template<typename T, typename... U>
concept to_stringable = requires(U... u) { (std::to_string(u) && ...); };
template <to_stringable... U>
auto to_string(const std::variant<U...>& value) {
return std::visit([](auto&& value) {
return std::to_string(std::forward<decltype(value)>(value));
}, value);
}
int main() {
std::variant<int, float> v = 42;
std::cout << to_string(v) << std::endl;
}
https://godbolt.org/z/W6znbvTzo
When you have:
template <to_stringable... U>
auto to_string(const std::variant<U0, U1>& value) {
This checks if each individual type satisfies to_stringable<T>, so it is essentially equivalent to:
template <to_stringable U0, to_stringable U1>
auto to_string(const std::variant<U0, U1>& value) {
And with just one argument T your concept is:
requires(T t) { (std::to_string(t)); };
However, with more than one argument, it looks like:
requires(T1 t1, T2 t2) { (std::to_string(t1) && std::to_string(t2)); }
Which doesn't work because you can't && two std::strings, so the constraint is not satisfied.
What you really want to do is fold over a constraint:
template <typename... U>
requires ((requires(U x) { std::to_string(x); }) && ...)
... Which gcc doesn't seem to implement properly because of a bug but you can get away with:
template <typename... U>
requires requires(U... x) { ((void) std::to_string(x), ...); }
You did not exactly convert it into a concept. This would be an exact conversion into a concept:
template<typename... U>
concept real_to_stringable = requires(U... u) { (std::to_string(u) && ...); };
template <typename ...U>
requires real_to_stringable<U...>
auto to_string(const std::variant<U...>& value) {
return std::visit([](auto&& value) {
return std::to_string(std::forward<decltype(value)>(value));
}, value);
}
And it gives the same error. For the same reason: std::to_string returns a basic_string. Which does not have a && overload. And therefore, &&ing them all together doesn't compile. So the constraint is not satisfied.
The reason your version of the concept code appears to work is because you didn't do it right. A concept whose first parameter is a type is a "type concept". A type concept can be used in place of a typename in certain circumstances as a shorthand for adding a requires constraint.
The first template parameter for such a concept is filled in with the type given as an argument to the template being constrained. The other template parameters for the concept are filled in by the other arguments provided at the point of the concept's use. But... you didn't provide any; to_stringable wasn't given other arguments in the template header. So the U in your to_stringable is always an empty set. Which means the constraint is always satisfied.
That is, template<to_stringable ...U> produces a requires constraint equivalent to requires (to_stringable<U> && ...), not requires to_stringable<U...>.
Consider this function:
template<typename T>
void f(T c) {
std::cout<<c<<std::endl;
}
You see that it will not compile for types which does not have an operator<< overload.
Now I want to write a function that acts like a fallback for this case.
/*Fallback*/
template<>
void f(T c) {
std::cout<<"Not Printing"<<std::endl;
}
How must this function be defined to do the job?
Pre-C++20
To have these overloads work in a fallback way, we can start by defining a trait that detects the validity of the expression involving operator <<
namespace detail {
template<typename T, typename = void>
struct streamable : std::false_type{};
template<typename T>
struct streamable<T, decltype(std::declval<std::ostream&>() << std::declval<T&>(), void())> : std::true_type {};
}
It's just your typical use of the detection idiom with as little extra library support as possible. Depending on the standard you are building against, this may be written in other ways (for instance std::void_t can be used, if available).
Now, the two overloads can be specified rather simply:
template<typename T>
auto f(T c) -> std::enable_if_t<detail::streamable<T>::value, void> {
std::cout<<c<<std::endl;
}
template<typename T>
auto f(T c) -> std::enable_if_t<!detail::streamable<T>::value, void> {
/// other code
}
Post C++20, concepts and constraints make it a whole lot easier. It can even be written ad-hoc:
template<typename T>
requires requires(std::ostream& os, T& c) { os << c; }
void f(T c) {
std::cout<<c<<std::endl;
}
template<typename T> // No extra step, subsumed by the above when possible
void f(T c) {
// other code
}
With concepts (C++20), we can achieve this like so:
template<typename T>
concept Streamable = requires(T t){std::declval<std::ostream&>() << t; };
template<Streamable T>
void f(T c) { std::cout << c << std::endl; }
/*Fallback*/
template<typename T>
void f(T c) { std::cout << "fallback" < <std::endl; }
Demo
Test:
struct Foo{};
int main()
{
Foo foo;
f(foo); // prints "fallback"
int a = 42;
f(a); // prints "42"
}
If you want to make doubly sure that your fallback will only happen if your type is not Streamable, you can constrain it, too:
template<typename T> requires (!Streamable<T>)
void f(T c) { /*...*/ }
You have several options of doing this. Arguably the most elegant way is to define your own type trait (similar to the ones in type_traits).
Let's define a is_streamable type trait. It takes two template arguments: S is the data type of the file stream (e.g. std::ostream or std::fstream or any other type that defines a custom streaming operator that is compatible with T) and secondly the data type of the object to be streamed into this file stream T:
template<typename S, typename T, typename = void>
struct is_streamable : std::false_type {
};
template<typename S, typename T>
struct is_streamable<S, T, decltype(std::declval<S&>() << std::declval<T&>(), void())> : std::true_type {
};
So far this type trait compiles with C++11 and onwards. For C++14 and later we can create a convenient alias for it similar to other type traits in C++17:
template <typename S, typename T>
static constexpr is_streamable_v = is_streamable<S,T>::value;
This type trait will now be the basis for the next step which will make use of SFINAE (C++11 onwards), constexpr if (C++17 onwards) or concepts (C++20).
In C++11 you could achieve this with either by putting the different implementations into partial specialisations of the same struct and call it with a helper function:
class f_imp {
};
template <typename T>
class f_imp<T,true> {
public:
static constexpr void imp(T c) {
std::cout << "streamable: " << c << std::endl;
}
};
template <typename T>
class f_imp<T,false> {
public:
static constexpr void imp(T c) {
std::cout << "not streamable" << std::endl;
}
};
template <typename T>
void f(T c) {
return f_imp<T,is_streamable<std::ostream,T>::value>::imp(c);
}
Try it here!
Alternatively you could apply SFINAE either by adding a second input parameter or applying it to the return type:
template<typename T, typename std::enable_if<is_streamable<std::ostream,T>::value>::type* = nullptr>
void f(T t) {
std::cout << "streamable" << std::endl;
}
template<typename T, typename std::enable_if<!is_streamable<std::ostream,T>::value>::type* = nullptr>
void f(T t) {
std::cout << "not streamable" << std::endl;
}
Try it here!
In C++17 you can actually use a constexpr if to avoid adding a second template argument and overloading of the function altogether. You can insert all the code inside the function and use if constexpr in combination with std::is_same_v and our is_streamable_v to decide at compile time which branch of our code each template type should take. This is in particular convenient if adding two specialisations would result in duplicate code but it might be harder to read.
template<typename T>
void f(T c) {
if constexpr (is_streamable_v<std::ostream,T>) {
std::cout << "streamable:" << c << std::endl;
} else {
// Fallback
std::cerr << "not streamable" << std::endl;
}
return;
}
Try it here!
Finally in C++20 you could use this type trait to define a concepts such as streamable and not_streamable:
template <typename T>
concept streamable = is_streamable_v<std::ostream,T>;
template <typename T>
concept not_streamable = !streamable<T>;
Then you can go on to apply them to your two overloads of the functions
template <streamable T>
void f(T c) {
std::cout << "streamable: " << c << std::endl;
}
template <not_streamable T>
void f(T c) {
std::cout << "not streamable" << std::endl;
}
Try it here!
Be aware that you will have to also apply the same logic to any custom streaming operator of a templated class, e.g. of a templated vector. Instead of declaring the operator for any template parameter typename T you would have to only declare it for streamable element types only. In C++20 for example with said streamable concept:
template <streamable T>
std::ostream& operator << (std::ostream& os, std::vector<T> const& vec) {
for (auto const& v: vec) {
os << v << " ";
}
return os;
}
Otherwise - as the template argument to the is_streamable operator is std::vector<T> as a whole - the compiler sees the operator << for std::vector<T> without checking if it would result in a compilation error for an unstreamable type T which does not define the operator << itself.
Try it here!
I am trying to implement a resource protection class which would combine data along with a shared mutex (actually, QReadWriteLock, but it's similar). The class must provide the method to apply a user-defined function to the data when the lock is acquired. I would like this apply method to work differently depending on the function parameter (reference, const reference, or value). For example, when the user passes a function like int (const DataType &) it shouldn't block exclusively as we are just reading the data and, conversely, when the function has the signature like void (DataType &) that implies data modification, hence the exclusive lock is needed.
My first attempt was to use std::function:
template <typename T>
class Resource1
{
public:
template <typename Result>
Result apply(std::function<Result(T &)> &&f)
{
QWriteLocker locker(&this->lock); // acquire exclusive lock
return std::forward<std::function<Result(T &)>>(f)(this->data);
}
template <typename Result>
Result apply(std::function<Result(const T &)> &&f) const
{
QReadLocker locker(&this->lock); // acquire shared lock
return std::forward<std::function<Result (const T &)>>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
But std::function doesn't seem to restrict parameter constness, so std::function<void (int &)> can easily accept void (const int &), which is not what I want. Also in this case it can't deduce lambda's result type, so I have to specify it manually:
Resource1<QList<int>> resource1;
resource1.apply<void>([](QList<int> &lst) { lst.append(11); }); // calls non-const version (ok)
resource1.apply<int>([](const QList<int> &lst) -> int { return lst.size(); }); // also calls non-const version (wrong)
My second attempt was to use std::result_of and return type SFINAE:
template <typename T>
class Resource2
{
public:
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f)
{
QWriteLocker locker(&this->lock); // lock exclusively
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const
{
QReadLocker locker(&this->lock); // lock non-exclusively
return std::forward<F>(f)(this->data);
}
private:
T data;
mutable QReadWriteLock lock;
};
Resource2<QList<int>> resource2;
resource2.apply([](QList<int> &lst) {lst.append(12); }); // calls non-const version (ok)
resource2.apply([](const QList<int> &lst) { return lst.size(); }); // also calls non-const version (wrong)
Mainly the same thing happens: as long as the object is non-const the mutable version of apply gets called and result_of doesn't restrict anything.
Is there any way to achieve this?
You may do the following
template <std::size_t N>
struct overload_priority : overload_priority<N - 1> {};
template <> struct overload_priority<0> {};
using low_priority = overload_priority<0>;
using high_priority = overload_priority<1>;
template <typename T>
class Resource
{
public:
template <typename F>
auto apply(F&& f) const
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
template <typename F>
auto apply(F&& f)
// -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
{
return apply_impl(std::forward<F>(f), high_priority{});
}
private:
template <typename F>
auto apply_impl(F&& f, low_priority) -> decltype(f(std::declval<T&>()))
{
std::cout << "ReadLock\n";
return std::forward<F>(f)(this->data);
}
template <typename F>
auto apply_impl(F&& f, high_priority) -> decltype(f(std::declval<const T&>())) const
{
std::cout << "WriteLock\n";
return std::forward<F>(f)(this->data);
}
private:
T data;
};
Demo
Jarod has given a workaround, but I'll explain why you cannot achieve that this regular way.
The problem is that:
Overload resolution prefers non-const member functions over const member functions when called from a non-const object
whatever object this signature void foo(A&) can accept, void foo(const A&) can also the same object. The latter even has a broader binding set than the former.
Hence, to solve it, you will have to at least defeat point 1 before getting to 2. As Jarod has done.
From your signatures (see my comment annotations):
template <typename F>
typename std::result_of<F (T &)>::type apply(F &&f) //non-const member function
{
return std::forward<F>(f)(this->data);
}
template <typename F>
typename std::result_of<F (const T &)>::type apply(F &&f) const //const member function
{
return std::forward<F>(f)(this->data);
}
When you call it like:
resource2.apply([](QList<int> &lst) {lst.append(12); }); //1
resource2.apply([](const QList<int> &lst) { return lst.size(); }); //2
First of all, remember that resource2 isn't a const reference. Hence, the non-const membr function of apply will always be prefered by Overload resolution.
Now, taking the case of the first call //1, Whatever that lambda is callable with, then then the second one is also callable with that object
A simplified mock-up of what you are trying to do is:
struct A{
template<typename Func>
void foo(Func&& f); //enable if we can call f(B&);
template<typename Func>
void foo(Func&& f) const; //enable if we can call f(const B&);
};
void bar1(B&);
void bar2(const B&);
int main(){
A a;
a.foo(bar1);
a.foo(bar2);
//bar1 and bar2 can be both called with lvalues
B b;
bar1(b);
bar2(b);
}
As I understand it, you want to discriminate a parameter that's a std::function that takes a const reference versus a non-constant reference.
The following SFINAE-based approach seems to work, using a helper specialization class:
#include <functional>
#include <iostream>
template<typename ...Args>
using void_t=void;
template<typename Result,
typename T,
typename lambda,
typename void_t=void> class apply_helper;
template <typename T>
class Resource1
{
public:
template <typename Result, typename lambda>
Result apply(lambda &&l)
{
return apply_helper<Result, T, lambda>::helper(std::forward<lambda>(l));
}
};
template<typename Result, typename T, typename lambda, typename void_t>
class apply_helper {
public:
static Result helper(lambda &&l)
{
std::cout << "T &" << std::endl;
T t;
return l(t);
}
};
template<typename Result, typename T, typename lambda>
class apply_helper<Result, T, lambda,
void_t<decltype( std::declval<lambda>()( std::declval<T>()))>> {
public:
static Result helper(lambda &&l)
{
std::cout << "const T &" << std::endl;
return l( T());
}
};
Resource1<int> test;
int main()
{
auto lambda1=std::function<char (const int &)>([](const int &i)
{
return (char)i;
});
auto lambda2=std::function<char (int &)>([](int &i)
{
return (char)i;
});
auto lambda3=[](const int &i) { return (char)i; };
auto lambda4=[](int &i) { return (char)i; };
test.apply<char>(lambda1);
test.apply<char>(lambda2);
test.apply<char>(lambda3);
test.apply<char>(lambda4);
}
Output:
const T &
T &
const T &
T &
Demo
The helper() static class in the specialized class can now be modified to take a this parameter, instead, and then use it to trampoline back into the original template's class's method.
As long as the capture lists of your lambdas are empty, you can rely on the fact that such a lambda decays to a function pointer.
It's suffice to discriminate between the two types.
It follows a minimal, working example:
#include<iostream>
template <typename T>
class Resource {
public:
template <typename Result>
Result apply(Result(*f)(T &)) {
std::cout << "non-const" << std::endl;
return f(this->data);
}
template <typename Result>
Result apply(Result(*f)(const T &)) const {
std::cout << "const" << std::endl;
return f(this->data);
}
private:
T data;
};
int main() {
Resource<int> resource;
resource.apply<void>([](int &lst) { });
resource.apply<int>([](const int &lst) -> int { return 42; });
}
I am trying to understad how std::less is implemented so I can say
template <typename T>
struct myless
{
constexpr bool operator()(const T &lhs, const T &rhs) const
{
return lhs < rhs;
}
};
template <typename A, typename B, typename U = myless> // std::less works
bool f(A a, B b, U u = U())
{
return u(a, b);
}
int main()
{
std::cout << std::boolalpha;
std::cout << f("AB/CD", "CD/AB") << '\n';
std::cout << f(100, 10) << '\n';
}
This doesn't work. Any suggestions?
There is a typo in f("AB/CD", "CD/AB",) (comma).
It should be typename U = myless<A> because myless is not in the std namespace.
Also the parameters should probably be passed by reference: bool f(const A& a, const B& b, const U& u = U()).
std::less needs both operands to be of the same type (logically), and myless is also defined like that. So using myless<A> for U would make it convert the B object to A for the comparing (by creating a temporary using its copy-constructor).
Since C++14, there is also the specialization std::less<void> where the operand can have different types, and a return type that is not bool. It maps one-to-one to what the operator< does. See http://en.cppreference.com/w/cpp/utility/functional/less_void .
Corrected version of the code:
#include <iostream>
template <typename T>
struct myless
{
constexpr bool operator()(const T &lhs, const T &rhs) const
{
return lhs < rhs;
}
};
template <typename A, typename B, typename U = myless<A>>
bool f(const A& a, const B& b, const U& u = U())
{
return u(a, b);
}
int main()
{
std::cout << std::boolalpha;
std::cout << f("AB/CD", "CD/AB") << '\n';
std::cout << f(100, 10) << '\n';
}
For a version that can have different types, and non-bool return type:
struct myless2 {
template<class T, class U>
constexpr auto operator()(const T& t, const U& u) const -> decltype(t < u) {
return t < u;
}
};
std::less<void> seems to also support r-value references, for when the operator< is defined like that (probably doing something else than comparation then.)
Your myless template takes a single type but your f function takes two types (i.e., they might be different types). It's possible to support this but it's more involved. Did you intend to do the following instead?
template<typename T, typename U = myless<T>>
bool f(T a, T b, U u = U())
{
return u(a, b);
}
Edit
As #vscoftco pointed out supporting different types may have been an intended use case. If different types were to be explicitly supported then I would have implemented it like this.
template<typename A, typename B, typename U = myless<typename std::common_type<A, B>::type>>
bool f(A a, B b, U u = U())
{
return u(a, b);
}
It also appears this solution is SFINAE compatible (C++17), http://en.cppreference.com/w/cpp/types/common_type.
If sizeof...(T) is zero or if there is no common type, the member type is not defined (std::common_type is SFINAE-friendly)
I'm trying to make a std::tuple that ends up holding either const references, or a value that was either copied or moved as appropriate where taking a reference wouldn't be sensible (e.g. temporaries).
So far I've got:
#include <functional>
#include <iostream>
template <typename ...Args>
struct foo {
const std::tuple<Args...> values;
};
template <typename T1, typename T2>
foo<T1, T2> make2(T1&& v1, T2&& v2) {
return foo<T1,T2>{std::tuple<T1, T2>(std::forward<T1>(v1),std::forward<T2>(v2))};
}
int main() {
double d1=1000;
double& d2 = d1;
auto f = make2(d2, 0);
std::cout << std::get<0>(f.values) << ", " << std::get<1>(f.values) << "\n";
d1 = -666;
std::get<0>(f.values)=0; // Allowed - how can I inject some more constness into references?
//std::get<1>(f.values) = -1; // Prohibited because values is const
std::cout << std::get<0>(f.values) << ", " << std::get<1>(f.values) << "\n";
}
Which is close, but not quite const enough for what I was hoping - I end up with a const std::tuple<double&, int> which of course allows me to modify the double that the tuple refers to.
I tried sprinkling some more constness into make2:
template <typename T1, typename T2>
foo<T1 const, T2 const> make2(T1&& v1, T2&& v2) {
return foo<T1 const,T2 const>{std::tuple<T1 const, T2 const>(std::forward<T1>(v1),std::forward<T2>(v2))};
}
That succeeded in making the int (i.e. non-reference) tuple member const (not terribly exciting given I can make the whole tuple const easily enough), but did nothing to the double& member. Why? How can I add that extra constness?
It didn't work because T1 const adds top-level const. I.e., it would make double &const, which is not different from double&. You need a inner const: "reference to const T1".
You could build this up with a combination of remove_reference, add_const and add_reference, or just write a small trait that puts the const in the right place:
template <typename T>
struct constify { using type = T; };
// needs a better name
template <typename T>
struct constify<T&> { using type = T const&; };
// and an alias for UX ;)
template <typename T>
using Constify = typename constify<T>::type;