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.)
Related
This actually compiles and works, but it's unclear to me why.
#include <iostream>
template <class T>
class LikeA
{
T m_val{};
public:
LikeA() = default;
explicit LikeA(T iv): m_val(std::move(iv)) {}
LikeA(LikeA<T> const &) = default;
LikeA(LikeA<T> &&) noexcept = default;
~LikeA() noexcept = default;
operator T const &() const { return m_val; }
LikeA<T> &operator=(T nv) { m_val = std::move(nv); return *this; }
LikeA<T> &operator=(LikeA<T> const &n) { m_val = n.m_val; return *this; }
LikeA<T> &operator=(LikeA<T> &&n) { m_val = std::move(n.m_val); return *this; }
};
template <class T>
T f (LikeA<T> i)
{
return i;
}
int main()
{
std::cout << f(LikeA{3.1415927}) << '\n'; // No template argument? Not a syntax error?
return 0;
}
I was previously calling f like f(3.1415927) before I let a lint checker talk me into making one of LikeAs constructors explicit. After that, of course, it couldn't implicitly convert the constant to a LikeA. If you just add braces (i.e. f({3.1415927}) the compiler still doesn't know what to select.
In my full code the actual template argument is a lot more verbose, so just for grins I put the template name LikeA in front of the brace initializers, fully expecting a syntax error.
To my surprise, it compiled and ran.
Since this was MSVC, at first I though it was just Microsoft lulling me into a sense of false security. But I tested it against several compilers (gcc, clang, zigcc) in Compiler Explorer, and it works on all of them.
How does C++ select the correct template specialization? On the surface, argument-dependent lookup would seem to be the answer, but notice there are no angle brackets, and the template doesn't have a default argument. I definitely remember this being a syntax error at some point in the past.
(Function template specialization without templated argument doesn't answer this because OP actually specifies the arguments).
The cppreference on function template arguments has a quick aside about omitting <> but this is a class template. The syntax here appears to require the angle brackets all the time.
Since C++17, compiler can automatically deduce the argument type of a template by using class template argument deduction (CTAD). You can skip defining the templates arguments explicitly if the constructor is able to deduce all template parameters.
So you simply write
int main()
{
std::vector v{2, 4, 6, 8}; // same as std::vector<int>
std::list l{1., 3., 5.}; // same as std::list<double>
std::pair p{false, "hello"}; // same as std::pair<bool, const char *>
std::cout << typeid(v).name() << std::endl;
std::cout << typeid(l).name() << std::endl;
std::cout << typeid(p).name() << std::endl;
}
Under MSVC, it produces the following output
class std::vector<int,class std::allocator<int> >
class std::list<double,class std::allocator<double> >
struct std::pair<bool,char const * __ptr64>
Kindly refer CTAD for more details.
I am trying to create a class that can be implicity cast to a variety of different types, both primitives and custom defined classes. One of the types that I want to be able to cast to is an std::string. Below is an example class that can cast to various different types. It throws the error "error: ambiguous overload for ‘operator=’". This is because std::string has an assignment operator from a CharT which the compiler can create from an int. My question is, is it possible to have a class that can both implicity convert to an integer or a string type?
class Test {
public:
operator double() const {
return 3.141592;
}
operator std::int64_t() const {
return -999;
}
operator std::uint64_t() const {
return 999;
}
operator bool() const {
return true;
}
operator std::string() const {
return "abcd";
}
};
int main(int argc, char** argv) {
std::string test_str = Test();
test_str = Test();
std::cout << test_str;
}
Interestingly, when I assign to test_str on the same line that I define it, the compiler throws no errors because it's using the simple constructor rather than an assignment operator, but it errors on the following line.
Any help would be much appreciated.
std::string has several overloaded operator=s with following parameters: std::string, const char *, char, std::initializer_list.
For your code to work, the compiler needs to choose one, but at least two are potentially suitable: the std::string one; and the char one, using an implicit conversion from one of your scalar operators. Even though the latter would cause an ambiguity later on, the compiler doesn't seem to reach that point.
The solution is to make a single templated conversion operator, and to restrict it to specific types with SFINAE:
Then following works:
template <auto V, typename, typename...>
inline constexpr auto value = V;
template <typename T, typename ...P>
concept one_of = (std::is_same_v<T, P> || ...);
class Test
{
public:
template <one_of<int, float, std::string> T>
operator T() const
{
if constexpr (std::is_same_v<T, int>)
return 42;
if constexpr (std::is_same_v<T, float>)
return 42;
else if constexpr (std::is_same_v<T, std::string>)
return "foo";
else
static_assert(value<false, T>, "This shouldn't happen.");
}
};
The static_assert isn't really necessary, since we already have requires. It merely provides a nicer error if you add more types to the requires and forget a branch.
Note value<false, T> instead of false. Trying to put false there directly would cause some compilers to emit an error unconditionally, even if the branch is not taken (this is allowed, but not required). Using an expression dependendent on T convinces the compiler to delay the test until an actual instantiation (because for all it knows, value could be specialized to become true for some T).
when I assign to test_str on the same line that I define it
This is an initialization, not assignment. It calls a constructor, not operator=.
Consider I have a custom type (which I can extend):
struct Foo {
int a;
string b;
};
How can I make an instance of this object assignable to a std::tie, i.e. std::tuple of references?
Foo foo = ...;
int a;
string b;
std::tie(a, b) = foo;
Failed attempts:
Overloading the assignment operator for tuple<int&,string&> = Foo is not possible, since assignment operator is one of the binary operators which have to be members of the left hand side object.
So I tried to solve this by implementing a suitable tuple-conversion operator. The following versions fail:
operator tuple<int,string>() const
operator tuple<const int&,const string&>() const
They result in an error at the assignment, telling that "operator = is not overloaded for tuple<int&,string&> = Foo". I guess this is because "conversion to any type X + deducing template parameter X for operator=" don't work together, only one of them at once.
Imperfect attempt:
Hence I tried to implement a conversion operator for the exact type of the tie:
operator tuple<int&,string&>() const Demo
operator tuple<int&,string&>() Demo
The assignment now works since types are now (after conversion) exactly the same, but this won't work for three scenarios which I'd like to support:
If the tie has variables of different but convertible types bound (i.e. change int a; to long long a; on the client side), it fails since the types have to fully match. This contradicts the usual use of assigning a tuple to a tuple of references which allows convertible types.(1)
The conversion operator needs to return a tie which has to be given lvalue references. This won't work for temporary values or const members.(2)
If the conversion operator is not const, the assignment also fails for a const Foo on the right hand side. To implement a const version of the conversion, we need to hack away const-ness of the members of the const subject. This is ugly and might be abused, resulting in undefined behavior.
I only see an alternative in providing my own tie function + class together with my "tie-able" objects, which makes me force to duplicate the functionality of std::tie which I don't like (not that I find it difficult to do so, but it feels wrong to have to do it).
I think at the end of the day, the conclusion is that this is one drawback of a library-only tuple implementation. They're not as magic as we'd like them to be.
EDIT:
As it turns out, there doesn't seem to be a real solution addressing all of the above problems. A very good answer would explain why this isn't solvable. In particular, I'd like someone to shed some light on why the "failed attempts" can't possibly work.
(1): A horrible hack is to write the conversion as a template and convert to the requested member types in the conversion operator. It's a horrible hack because I don't know where to store these converted members. In this demo I use static variables, but this is not thread-reentrant.
(2): Same hack as in (1) can be applied.
Why the current attempts fail
std::tie(a, b) produces a std::tuple<int&, string&>.
This type is not related to std::tuple<int, string> etc.
std::tuple<T...>s have several assignment-operators:
A default assignment-operator, that takes a std::tuple<T...>
A tuple-converting assignment-operator template with a type parameter pack U..., that takes a std::tuple<U...>
A pair-converting assignment-operator template with two type parameters U1, U2, that takes a std::pair<U1, U2>
For those three versions exist copy- and move-variants; add either a const& or a && to the types they take.
The assignment-operator templates have to deduce their template arguments from the function argument type (i.e. of the type of the RHS of the assignment-expression).
Without a conversion operator in Foo, none of those assignment-operators are viable for std::tie(a,b) = foo.
If you add a conversion operator to Foo,
then only the default assignment-operator becomes viable:
Template type deduction does not take user-defined conversions into account.
That is, you cannot deduce template arguments for the assignment-operator templates from the type Foo.
Since only one user-defined conversion is allowed in an implicit conversion sequence, the type the conversion operator converts to must match the type of the default assignment operator exactly. That is, it must use the exact same tuple element types as the result of std::tie.
To support conversions of the element types (e.g. assignment of Foo::a to a long), the conversion operator of Foo has to be a template:
struct Foo {
int a;
string b;
template<typename T, typename U>
operator std::tuple<T, U>();
};
However, the element types of std::tie are references.
Since you should not return a reference to a temporary,
the options for conversions inside the operator template are quite limited
(heap, type punning, static, thread local, etc).
There are only two ways you can try to go:
Use the templated assignment-operators:
You need to publicly derive from a type the templated assignment-operator matches exactly.
Use the non-templated assignment-operators:
Offer a non-explicit conversion to the type the non-templated copy-operator expects, so it will be used.
There is no third option.
In both cases, your type must contain the elements you want to assign, no way around it.
#include <iostream>
#include <tuple>
using namespace std;
struct X : tuple<int,int> {
};
struct Y {
int i;
operator tuple<int&,int&>() {return tuple<int&,int&>{i,i};}
};
int main()
{
int a, b;
tie(a, b) = make_tuple(9,9);
tie(a, b) = X{};
tie(a, b) = Y{};
cout << a << ' ' << b << '\n';
}
On coliru: http://coliru.stacked-crooked.com/a/315d4a43c62eec8d
As the other answers already explain, you have to either inherit from a tuple (in order to match the assignment operator template) or convert to the exact same tuple of references (in order to match the non-templated assignment operator taking a tuple of references of the same types).
If you'd inherit from a tuple, you'd lose the named members, i.e. foo.a is no longer possible.
In this answer, I present another option: If you're willing to pay some space overhead (constant per member), you can have both named members and tuple inheritance simultaneously by inheriting from a tuple of const references, i.e. a const tie of the object itself:
struct Foo : tuple<const int&, const string&> {
int a;
string b;
Foo(int a, string b) :
tuple{std::tie(this->a, this->b)},
a{a}, b{b}
{}
};
This "attached tie" makes it possible to assign a (non-const!) Foo to a tie of convertible component types. Since the "attached tie" is a tuple of references, it automatically assigns the current values of the members, even though you initialized it in the constructor.
Why is the "attached tie" const? Because otherwise, a const Foo could be modified via its attached tie.
Example usage with non-exact component types of the tie (note the long long vs int):
int main()
{
Foo foo(0, "bar");
foo.a = 42;
long long a;
string b;
tie(a, b) = foo;
cout << a << ' ' << b << '\n';
}
will print
42 bar
Live demo
So this solves problems 1. + 3. by introducing some space overhead.
This kind of does what you want right? (assumes that your values can be linked to the types of course...)
#include <tuple>
#include <string>
#include <iostream>
#include <functional>
using namespace std;
struct Foo {
int a;
string b;
template <template<typename ...Args> class tuple, typename ...Args>
operator tuple<Args...>() const {
return forward_as_tuple(get<Args>()...);
}
template <template<typename ...Args> class tuple, typename ...Args>
operator tuple<Args...>() {
return forward_as_tuple(get<Args>()...);
}
private:
// This is hacky, may be there is a way to avoid it...
template <typename T>
T get()
{ static typename remove_reference<T>::type i; return i; }
template <typename T>
T get() const
{ static typename remove_reference<T>::type i; return i; }
};
template <>
int&
Foo::get()
{ return a; }
template <>
string&
Foo::get()
{ return b; }
template <>
int&
Foo::get() const
{ return *const_cast<int*>(&a); }
template <>
string&
Foo::get() const
{ return *const_cast<string*>(&b); }
int main() {
Foo foo { 42, "bar" };
const Foo foo2 { 43, "gah" };
int a;
string b;
tie(a, b) = foo;
cout << a << ", " << b << endl;
tie(a, b) = foo2;
cout << a << ", " << b << endl;
}
Major downside is that each member can only be accessed by their types, now, you could potentially get around this with some other mechanism (for example, define a type per member, and wrap the reference to the type by the member type you want to access..)
Secondly the conversion operator is not explicit, it will convert to any tuple type requested (may be you don't want that..)
Major advantage is that you don't have to explicitly specify the conversion type, it's all deduced...
This code works for me. I'd love it if someone could point out anything wrong with it.
Simple Version on Compiler Explorer
More Generic Version on Compiler Explorer
#include <tuple>
#include <cassert>
struct LevelBounds final
{
int min;
int max;
operator std::tuple<int&, int&>() { return {min, max}; }
};
int main() {
int a, b;
auto lb = LevelBounds{30, 40};
std::tie(a, b) = lb;
assert(30 == a);
assert(40 == b);
return 0;
}
In the C++ 20 Standard the constructor of the class template std::reference_wrapper is a template.
template<class U>
constexpr reference_wrapper(U&&) noexcept(see below );
while the assignment operator is not a template
constexpr reference_wrapper& operator=(const reference_wrapper& x) noexcept;
What is the reason that this difference (template and non-template) between these special member functions exists?
On the other hand, I tried the following progarm using Visual C++ 2019.
#include <iostream>
#include <functional>
struct A
{
void f() const { std::cout << "A::f()\n"; }
virtual void g() const { std::cout << "A::g()\n"; }
};
struct B : A
{
void f() const { std::cout << "B::f()\n"; }
void g() const override { std::cout << "B::g()\n"; }
};
int main()
{
B b;
std::reference_wrapper<A> r( b );
r.get().f();
r.get().g();
r = std::reference_wrapper<B>( b );
}
and the compiler did not issue an error message relative to the assignment operator.
Is it a bug of the compiler or have I missed something?
If std::reference_wrapper<T> had a single constructor accepting T&, then it would lead to bugs like std::reference_wrapper<const T> being able to bind to temporaries of type T. So, originally there were two constructors (other than the copy constructor): one taking T&, and another taking T&&, which was defined as deleted, ensuring that a compile error would occur.
However, it was pointed out that this is not really what we want: it would be better if std::reference_wrapper<T> would have no constructor at all that accepts an rvalue of type T, as opposed to a deleted constructor. This is LWG 2993. (You'll notice that this issue is mentioned at the bottom of the cppreference page.) Thus, the constructor was changed to a template that is SFINAE-disabled as appropriate.
Once these issues are solved for the constructor, the assignment operator only needs to have a single overload taking reference_wrapper. The conversion issues will be handled by the constructor logic when the compiler forms the implicit conversion sequence.
I would like to know why implicit type conversion doesn't work with outside operator overloading on class templates. Here is the working, non-templated version:
class foo
{
public:
foo() = default;
foo(int that)
{}
foo& operator +=(foo rhs)
{
return *this;
}
};
foo operator +(foo lhs, foo rhs)
{
lhs += rhs;
return lhs;
}
As expected, the following lines compile correctly:
foo f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // OK
f = 5 + f; // OK
On the other hand, when class foo is declared as a simple template like this:
template< typename T >
class foo
{
public:
foo() = default;
foo(int that)
{}
foo& operator +=(foo rhs)
{
return *this;
}
};
template< typename T >
foo< T > operator +(foo< T > lhs, foo< T > rhs)
{
lhs += rhs;
return lhs;
}
The following lines compile with errors:
foo< int > f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // Error (no match for operator+)
f = 5 + f; // Error (no match for operator+)
I would like to understand why the compiler (GCC 4.6.2) is unable to perform implicit type conversion using the converting constructor for the template version of the class. Is that the expected behaviour? Apart from manually creating all the necessary overloads, is there any workaround for this?
The reason it does not just work is that implicit type conversions (that is, via constructors) do not apply during template argument deduction.
But it works if you make the outside operator a friend since then the type T is know, allowing the compiler to investigate what can be casted to make the arguments match.
I made an example based on yours (but removed C++11 stuff), inspired by Item 46 (a rational number class) in Scott Meyers Effective C++ (ed 3). Your question is almost an exact match to that item. Scott also notes that ... "this use of friend is not related to the access of non-public parts of the class."
This will also allow work with mixes of foo< T >, foo< U > etc as long as T and U can be added etc.
Also look at this post: C++ addition overload ambiguity
#include <iostream>
using namespace std;
template< class T >
class foo
{
private:
T _value;
public:
foo() : _value() {}
template <class U>
foo(const foo<U>& that) : _value(that.getval()) {}
// I'm sure this it can be done without this being public also;
T getval() const { return _value ; };
foo(const T& that) : _value(that) {}
friend const foo operator +(foo &lhs,const foo &rhs)
{
foo result(lhs._value+rhs._value);
return result;
};
friend const foo operator +(foo &lhs,const T &rhsval)
{
foo result(lhs._value+rhsval);
return result;
};
friend const foo operator +(const T &lhsval,foo &rhs)
{
foo result(lhsval+rhs._value);
return result;
};
friend foo& operator +=(foo &lhs,const foo &rhs)
{
lhs._value+=rhs._value;
return lhs;
};
friend std::ostream& operator<<(std::ostream& out, const foo& me){
return out <<me._value;
}
};
int main(){
foo< int > f, g;
foo< double > dd;
cout <<f<<endl;
f = f + g;
cout <<f<<endl;
f += 3 ;
cout <<f<<endl;
f = f + 5;
cout <<f<<endl;
f = 7 + f;
cout <<f<<endl;
dd=dd+f;
cout <<dd<<endl;
dd=f+dd;
cout <<dd<<endl;
dd=dd+7.3;
cout <<dd<<endl;
}
I put this question to the library authors at MS and got an extremely informative response from Stephan Lavavej, so I give him full credit for this information.
The compile error you get in the template case is due to the fact that template argument deduction runs before overload resolution, and template argument deduction needs exact matches to add anything to the overload set.
In detail, template argument deduction looks at each pair of parameter type P and argument type A, and tries to find template substitutions that will make A exactly match P. After finding matches for each argument, it checks for consistency (so that if you call bar(foo<T>, foo<T>) with T=int for the first parameter and T=double as the second, it also fails). Only after exact, consistent matches are successfully substituted in the function signature is that signature added to the set of candidate functions for overload resolution.
Only after all ordinary functions (found through name lookup) and matching function template signatures have been added to the overload set is overload resolution run, at which point all of these function signatures are evaluated for a "best match", during which time implicit conversions will be considered.
For the operator+(foo<T>, foo<T>) case with foo<int> + 5, template argument deduction can find no substitution for T that will make the expression foo<T> exactly match int, so that overload of operator+ gets tossed out as a candidate and the implicit conversion is never even seen.
The opinion here seems to be that this is generally a good thing, as it makes templates much more predictable, leaving the realm of strange implicit behaviors to overload resolution.
The standard has plenty to say about this at:
14.8.2.1 Deducing template arguments from a function call
"Template argument deduction is done by comparing each function template parameter type (call it P) with
the type of the corresponding argument of the call (call it A) as described below. ...
... In general, the deduction process attempts to find template argument values that will make the deduced A
identical to A (after the type A is transformed as described above)"
It goes on to list a few special cases where this rule has exceptions involving cv-qualifiers (so T& will be compatible with const T&), and matching of derived classes (it can in some cases match Derived& to Base&) but basically, exact matching is the rule.
All possible foo<T> are equally valid conversions from int since the constructor takes int, not the template type. The compiler isn't able to use the other parameter in the operator to guess which one you might mean, so you get the error. If you explicitly tell it which instantiation you want I believe it would work.