Mixing delete keyword with concepts and constraints - c++

In my question I received an answer which suits me, but I don't understand how does it work.
Especially, I don't understand how delete keyword along with concepts remove overloads for operator<<.
(I will paste piece-by-piece, refactored version of the code from the accepted answer.)
enum class LogLevel
{
info,
warning,
error
};
template<typename T>
concept HasLogMethodReturningReferenceToSelf = requires(T v)
{
{
v.log(LogLevel{})
} -> std::convertible_to<T&>;
};
So, here we define a concept, which checks whether a type has method log(), which takes LogLevel as a parameter and returns convertible to reference to self.
Then we delete operator<< overloads (block implicit function generation and explicit overloads) for overloads which have on the left-side of << a type which satisfies HasLogMethodReturningReferenceToSelf and on the right-side of << a type which is not a std::string:
template<HasLogMethodReturningReferenceToSelf T, class U>
requires(!std::convertible_to<U, std::string>) auto operator<<(T, U) = delete;
I don't understand when the overloads would be deleted? During "instantiation" of the concept for some specific type T? Because, not for each type which satisfies the criteria (it would break the codebase?)?
Because later, another concept is defined, which checks for output stream operator overloads for basic types:
template<typename T>
concept HasOutputStreamOperatorOverloadsForBasicTypes =
requires(T v, unsigned unsigned_, int int_, float float_, unsigned char unsigned_char_, char char_)
{
{
v << "string literal" //
<< unsigned_ //
<< int_ //
<< float_ //
<< unsigned_char_ //
<< char_ //
} -> std::convertible_to<T&>;
};
Finally, we define a Loggable concept:
template<typename T>
concept Loggable = HasLogMethodReturningReferenceToSelf<T> && HasOutputStreamOperatorOverloadsForBasicTypes<T>;
Normally, I would define one big Loggable concept:
template<typename T>
concept Loggable = requires(T v)
{
{
v.log(LogLevel{})
} -> std::convertible_to<T&>;
{
v << "string literal" //
<< unsigned_ //
<< int_ //
<< float_ //
<< unsigned_char_ //
<< char_ //
} -> std::convertible_to<T&>;
};
And then somehow restrict implicit conversion. I guess that the author of the answer had to split those requires expressions to two, because otherwise, when I use the latter Loggable definition and then delete the overloads after the definition:
template<Loggable T, class U>
requires(!std::convertible_to<U, std::string>) auto operator<<(T, U) = delete;
I get compile error:
fatal error: recursive template instantiation exceeded maximum depth of 1024
Why?

I don't understand when the overloads would be deleted? During
"instantiation" of the concept for some specific type T? Because, not
for each type which satisfies the criteria (it would break the
codebase?)?
When we check whether v << unsigned_ is well-formed in the requires clause, if your Logger class does not have the unsigned overload of operator<<, the global operator<< defined before concepts will be selected since U will be directly instantiated as unsigned and there is no implicit conversion, so it has a higher priority.
And because the global operator<< is deleted, so v << unsigned_ is ill-formed and the constraint is not satisfied.

Related

Function call ambiguity in templatized code

Here is a (very distilled) use case and code example (sorry if it doesn't look too minimal, I couldn't figure out what else to exclude. The complete 'just compile' code can be found here: https://gcc.godbolt.org/z/5GMEGKG7T)
I want to have a "special" output stream, which, for certain user types, does special treatment during stream processing.
This special stream should be able to stream a "special" type with "special" treatment, as well as any other type which could be streamed through conventional streams - in the latter case, we simply use the conventional stream directly
struct Type { }; // Special type
struct Stream { // Special stream
// In this distilled example, we could achieve the same result
// by overriding << for ostream and Type, but the actual use case is wider
// and requires usage of the special stream type
// For illustration purposes only, we use this overload to cout "T!" for Types
Stream& stream(Type t) { std::cout << "T!"; return *this;}
// For any type for which cout << T{} is a valid operation, use cout for streaming
template<class T>
auto stream(const T& t) -> decltype(std::cout << std::declval<T>(), std::declval<Stream&>())
{
std::cout << t; return *this;
}
};
// Consumes every T for which Stream.stream is defined - those would be Type
// as well as anything else which can be sent to cout
template<class T>
auto operator<<(Stream& s, const T& t) -> decltype(s.stream(t))
{
s.stream(t);
return s;
}
This code works as expected:
void foo()
{
Stream stream;
stream << 10 << Type{};
}
Now I want to define a different struct, which has a member of Type in it, and a streaming operator for the same struct:
struct Compound {
Type t;
int i;
};
// We want the `<<` operator of the struct templated, because we want it to work with
// any possible stream-like object out there
template<class S>
S& operator<<(S& s, Compound comp)
{
s << comp.t << " " << comp.i;
return s;
};
Unfortunately, this doesn't work well. An attempt to use this << operator:
void bar(Compound comp)
{
Stream s;
s << comp;
}
Triggers an ambiguity:
error: ambiguous overload for 'operator<<' (operand
types are 'Stream' and 'Compound')
note: candidate: 'decltype (s.Stream::stream(t)) operator<<(Stream&, const T&)
note: candidate: 'S& operator<<(S&, Compound) [with S = Stream]'
That makes sense, since after defining templated operator for << for Compound, it could be streamed via cout, as well as through this new operator!
I can certainly work aorund this by making << for Compound non-templated, and instead use the special Stream as a stream argument - but I'd like to avoid it.
Is there a way I can preserve the templated << for Compound and still avoid the ambiguity?
So you need to lower the priority of operator<<(Stream& s, const T& t) to make it the last resort.
You can do it by making it (seemingly) less specialized, by templating the first parameter:
auto operator<<(std::same_as<Stream> auto &s, const auto &t) -> decltype(s.stream(t))
{
s.stream(t);
return s;
}
The change I made to the second parameter is unimportant, it's just for terseness.
Some extra boilerplate maybe?
template<class T> struct IsDefault: std::true_type {};
template<class T> concept Default = IsDefault<T>::value;
struct Stream {
template<Default T> Stream &operator<<(T &&t) {
return std::cout << t, *this;
}
};
struct Compound {};
template<> struct IsDefault<Compound>: std::false_type {};
Stream &operator<<(Stream &s, Compound &&c) { return s << "Compound"; }

C++ concept member check type ambiquity with reference

I am learning C++ concepts, and I have an obnoxious problem:
I do not know how to differentiate between member variable being variable of type int and member variable being a int&.
Reason for this is that check I am using is using instance.member syntax and in C++ that returns a reference.
Full example:
#include <iostream>
#include <concepts>
template<typename T>
void print(T t) {
std::cout << "generic" << std::endl;
}
template<typename T>
requires requires(T t){
{t.val} -> std::same_as<int&>;
}
void print(T t) {
std::cout << "special" << std::endl;
}
struct S1{
int bla;
};
struct S2{
int val = 47;
};
int x = 47;
struct S3{
int& val=x;
};
int main()
{
print(4.7);
print(S1{});
print(S2{});
print(S3{});
}
I wish print(S3{}) would be handled by generic case, not the special one.
Note that changing my requires stuff to:
{t.val} -> std::same_as<int>;
makes S2 not match the template so that does not work(like I said I think that member access in C++ returns a reference).
Is there a way to fix this?
The problem here is that expression concept checks use decltype((e)) in the check rather than decltype(e) (the extra parentheses matter).
Because t.val is an lvalue of type int (expressions never have reference type), decltype((t.val)) is int& regardless, as you've discovered.
Instead, you need to explicitly use the single-paren syntax:
template <typename T>
requires requires (T t) {
requires std::same_as<decltype(t.val), int&>;
}
void print(T t) {
std::cout << "special" << std::endl;
}
Or
template <typename T>
requires std::same_as<decltype(T::val), int&>
The solution is:
template <typename T>
requires requires(T t)
{
requires std::is_same_v<decltype(t.val), int>; // or `int &` for references
}
void print(T t)
Clang has a bug that causes {T::val} -> std::same_as<int> to work as well, even though the type of the lhs is said to be determined as if by decltype((...)), which should return int & here.
Note that "member access in C++ returns a reference" is false. It can't be the case because expressions can't have reference types. When you write a function with a reference return type, calling it produces an lvalue of a non-reference type.
decltype will add reference-ness to the expression types depending on value category (& for lvalues, && for xvalues, nothing for prvalues). That's why people often think that expressions can have reference types.
It also has a special rule for variables (as opposed to general expressions), which causes it to return the variable type as written, disregarding the expression type & value category. And apparently t.val counts as a variable for this purpose.

Type conversion in class template instantiation

I have a template class item which stores objects of various types T. It also attaches attributes to those objects in instantiation/initialization.
One special thing I want to achieve is that whenever item sees a const char *, it deems and stores it as a std::string. This could be done, as follows.
But in type checking, I found an item instantiated from a const char * is still different in type from an item instantiated from a std::string. Please see the last line with comment false, which I want to make true.
#include <iostream>
#include <string>
#include <type_traits>
using namespace std;
template<typename T>
using bar = typename std::conditional<std::is_same<T, const char *>::value,
string, T>::type;
template<typename T>
class item
{
bar<T> thing;
// other attributes ...
public:
item(T t) : thing(t) {}
// other constructors ...
bar<T> what() const
{
return thing;
}
};
int main()
{
auto a = item("const char *"); // class template argument deduction (C++17)
auto b = item(string("string")); // class template argument deduction (C++17)
cout << std::boolalpha;
cout << (typeid(a.what()) == typeid(b.what())) << endl; // true
cout << (typeid(a) == typeid(b)) << endl; // false
}
My question is: is it possible to make any change to the template class item so that an item instantiated from a const char * becomes the same in type with an item instantiated from a std::string?
In other words, can I make any change to the design of the template class item so that typeid(a) == typeid(b) evaluates to true ?
Thank you !
Note: This follows up a previous question on template function. But I think there's something intrinsically different that it deserves a stand-alone question.
Edit: My goal is to change the design of the template class item (e.g. item signatures), not the code in main, which is assumed to be supplied by users. I want to make life easier for the users of item, by not asking them to explicitly supply type T in instantiation. This is meant to be done by C++17 template class argument deduction or some equivalent workarounds.
Update: Thank you all! Special thanks to #xskxzr, whose one-liner exactly solves my question. With user-defined deduction guides for class template argument deduction, I don't even need the bar<T> technique in my previous code. I put updated code below for your comparison.
#include <iostream>
#include <string>
using namespace std;
template<typename T>
class item
{
// UPDATE: no bar<T> needed any more
T thing;
// other attributes ...
public:
item(T t) : thing(t) {}
// other constructors ...
// UPDATE: no bar<T> needed any more
T what() const
{
return thing;
}
};
item(const char *) -> item<std::string>; // UPDATE: user-defined deduction guide !
int main()
{
auto a = item("const char *"); // class template argument deduction (C++17)
auto b = item(string("string")); // class template argument deduction (C++17)
cout << std::boolalpha;
cout << (typeid(a.what()) == typeid(b.what())) << endl; // true
cout << (typeid(a) == typeid(b)) << endl; // UPDATE: now true !
}
You can add a user-defined deduction guide:
item(const char *) -> item<std::string>;
With this deduction guide, a will be deduced to be item<std::string>.
No, you can't directly make the typeid of two templated objects using different template arguements be the same.
But to achieve your end goal you can use a factory like pattern. It could look something like this:
template<typename T, typename R = T>
item<R> make_item(T&& t)
{
return item<T>(std::forward<T>(t));
}
// Specialization for const char *
template<>
item<std::string> make_item(const char *&& str)
{
return item<std::string>(str);
}
The downside with this approach is that you'll need to construct all of your objects with this factory. And if you have a lot of exceptions you'll need to make a specialization for each exception.
This is more a guess than an answer, but I'd say no. Templates are expanded at compile time, so because you are creating an
item<const char*>
and an
item<std::string>
then the code that gets expanded looks something like
class item1
{
bar<const char*> thing;
// other attributes ...
public:
item(const char* t) : thing(t) {}
// other constructors ...
bar<const char*> what() const
{
return thing;
}
};
class item2
{
bar<std::string> thing;
// other attributes ...
public:
item(std::string t) : thing(t) {}
// other constructors ...
bar<std::string> what() const
{
return thing;
}
};
(More or less; they wouldn't actually be called item1 and item2)
How you chose to evaluate these two types is up to you, but to the compiler they are in fact two different types.
Ok, I'd never seen or used std::conditional before so I wasn't sure what that was doing, but after reading up on it and playing around with your code I did get it to "work" by using
bar<T>
as the template type. So instead of
auto a = item<const char*>("const char *");
auto b = item<string>(string("string"));
I did
auto a = item<bar<const char*>>("const char *");
auto b = item<bar<string>>(string("string"));
The thing is you need the template type to be the same in both cases, meaning the type needs to resolve to std::string before the template gets expanded. As long as you use your conditional, you can define any type.
auto c = item<bar<int>>(5);
Not sure that's a good solution (which is why I said "work"), but see my other answer about the class types actually being different.

Passing lambdas directly to functions

I'm writing an Option class which represents a value that may or may not exist. The if_opt function is intended to take an Option and a function which will be called on the value held in the Option, but only if the value exists.
template <class T>
class Option {
private:
std::shared_ptr<T> m_value;
public:
explicit operator bool()const noexcept
{
return (bool)m_value;
}
Option() = default;
explicit Option(T value)
{
m_value = std::make_shared<T>(value);
}
template <class U>
friend void if_opt(Option<U>&, std::function<void(U&)>);
};
template <class T>
void if_opt(Option<T>& opt, std::function<void(T&)> f)
{
if (opt) f(*opt.m_value);
};
I've noticed that this works if I use it like so:
Option<int> none;
Option<int> some(10);
function<void(int&)> f1 = [](int& none)
{
cout << "This should never be reached" << endl;
};
function<void(int&)> f2 = [](int& some)
{
cout << "The value of some is: " << some << endl;
};
if_opt(none, f1);
if_opt(some, f2);
But I'd like to be able to put the lambda expression directly in the call, but when I do:
if_opt(none, [](int&)
{
cout << "This should never be reached" << endl;
});
if_opt(some, [](int& some)
{
cout << "The value of some is: " << some << endl;
});
I get an error:
error: no matching function for call to 'if_opt(Option<int>&, main()::<lambda(int&)>)'
I know that the type of a lambda expression is undefined in the standard, and that it merely has to be assignable to std::function<R(T)>, so this sort of makes sense, but is there a way that I can get the lambda argument to implicitly convert to a std::function<void(T&)> so that I can define the lambda in the call to if_opt the way I attempted?
std::function<Sig> is a type erasure tool. It erases (almost) everything about the value it stores excpet that it can be invoked with Sig.
Template argument deduction takes a passed in type and deduces what types should be used, then the template function is generated and (usually) called.
These are almost inverses of each other. Doing deduction on a type erasure template is code smell, and almost always a bad idea.
So that is your fundamental design error.
There are a number of ways to fix your code.
First, if_opt shouldn't be a template.
friend void if_opt(Option<T>& opt, std::function<void(T&)> f){
if (opt) f(*opt.m_value);
}
this creates what I call a Koenig friend. You have to define the body inline. Really, that U type is pointless, and can even lead to errors in some cases.
But the type erasure here is also pointless. Fixing that returns the template, but now for a good reason.
template<class F>
friend void if_opt(Option<T>& opt, F&& f){
if (opt) f(*opt.m_value);
}
this is a better design.
You can go and invest in SFINAE overload resolution code, but I wouldn't bother.
template<class F,
std::conditional_t<true, bool,std::result_of_t<F&(T&)>> = true
>
friend void if_opt(Option<T>& opt, F&& f){
if (opt) f(*opt.m_value);
}
the above is obscure and has minimal marginal advantages over the one above it.

Conversion operator template specialization

Here's a largely academic exercise in understanding conversion operators, templates and template specializations. The conversion operator template in the following code works for int, float, and double, but fails when used with std::string... sort of. I've created a specialization of the conversion to std::string, which works when used with initialization std::string s = a;, but fails when used with a cast static_cast<std::string>(a).
#include <iostream>
#include <string>
#include <sstream>
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
template <typename T>
operator T() { return y; };
};
template<>
MyClass::operator std::string() {
std::stringstream ss;
ss << y << " bottles of beer.";
return ss.str();
}
int main () {
MyClass a(99);
int i = a;
float f = a;
double d = a;
std::string s = a;
std::cerr << static_cast<int>(a) << std::endl;
std::cerr << static_cast<float>(a) << std::endl;
std::cerr << static_cast<double>(a) << std::endl;
std::cerr << static_cast<std::string>(a) << std::endl; // Compiler error
}
The above code generates a compiler error in g++ and icc, both complaining that no user-defined conversion is suitable for converting a MyClass instance to a std::string on the static_cast (C-style casts behave the same).
If I replace the above code with explicit, non-template versions of the conversion operator, everything is happy:
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
operator double() {return y;}
operator float() {return y;}
operator int() {return y;}
operator std::string() {
std::stringstream ss;
ss << y << " bottles of beer.";
return ss.str();
}
};
What is wrong with my template specialization for std::string? Why does it work for initialization but not casting?
Update:
After some template wizardry by #luc-danton (meta-programming tricks I'd never seen before), I have the following code working in g++ 4.4.5 after enabling experimental C++0x extensions. Aside from the horror of what is being done here, requiring experimental compiler options is reason enough alone to not do this. Regardless, this is hopefully as educational for others as it was for me:
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
operator std::string() { return "nobody"; }
template <
typename T
, typename Decayed = typename std::decay<T>::type
, typename NotUsed = typename std::enable_if<
!std::is_same<const char*, Decayed>::value &&
!std::is_same<std::allocator<char>, Decayed>::value &&
!std::is_same<std::initializer_list<char>, Decayed>::value
>::type
>
operator T() { return y; }
};
This apparently forces the compiler to choose the conversion operator std::string() for std::string, which gets past whatever ambiguity the compiler was encountering.
You can reproduce the problem by just using
std::string t(a);
Combined with the actual error from GCC (error: call of overloaded 'basic_string(MyClass&)' is ambiguous) we have strong clues as to what may be happening: there is one preferred conversion sequence in the case of copy initialization (std::string s = a;), and in the case of direct initialization (std::string t(a); and static_cast) there are at least two sequences where one of them can't be preferred over the other.
Looking at all the std::basic_string explicit constructors taking one argument (the only ones that would be considered during direct initialization but not copy initialization), we find explicit basic_string(const Allocator& a = Allocator()); which is in fact the only explicit constructor.
Unfortunately I can't do much beyond that diagnostic: I can't think of a trick to discover is operator std::allocator<char> is instantiated or not (I tried SFINAE and operator std::allocator<char>() = delete;, to no success), and I know too little about function template specializations, overload resolution and library requirements to know if the behaviour of GCC is conforming or not.
Since you say the exercise is academic, I will spare you the usual diatribe how non-explicit conversion operators are not a good idea. I think your code is a good enough example as to why anyway :)
I got SFINAE to work. If the operator is declared as:
template <
typename T
, typename Decayed = typename std::decay<T>::type
, typename = typename std::enable_if<
!std::is_same<
const char*
, Decayed
>::value
&& !std::is_same<
std::allocator<char>
, Decayed
>::value
&& !std::is_same<
std::initializer_list<char>
, Decayed
>::value
>::type
>
operator T();
Then there is no ambiguity and the code will compile, the specialization for std::string will be picked and the resulting program will behave as desired. I still don't have an explanation as to why copy initialization is fine.
static_cast here is equivalent of doing std::string(a).
Note that std::string s = std::string(a); doesn't compile either. My guess is, there are plenty of overloads for the constructor, and the template version can convert a to many suitable types.
On the other hand, with a fixed list of conversions, only one of those matches exactly a type that the string's constructor accepts.
To test this, add a conversion to const char* - the non-templated version should start failing at the same place.
(Now the question is why std::string s = a; works. Subtle differences between that and std::string s = std::string(a); are only known to gods.)