I am trying to implement custom reference in C++. What I want to achieve is to have reference, which does not need to be set at creation. It looks like this
template<typename T>
class myreference
{
public:
myreference() : data(), isset(false) { }
explicit myreference(const T& _data) : data(&_data), isset(true) { }
myreference<T>& operator=(T& t)
{
if (!isset)
{
isset= true;
data = &t;
}
else
*data = t;
return *this;
}
operator T() const { return *data; }
operator T&() { return *data; }
private:
T* data;
bool isset;
};
It works fine. I can do this, except for last statement.
myreference<int> myref;
int data = 7, test = 3;
myref = data; // reference is set
myref = test; // data is now 3
int& i = myref;
i = 4; // data is now 4
cout << myref; // implicit int conversion
myref = 42; // error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int'
Full error
error C2679: binary '=' : no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)
1> d:\...\main.cpp(33): could be 'myreference<int> &myreference<int>::operator =(const myreference<int> &)'
1> d:\...\main.cpp(16): or 'myreference<int> &myreference<int>::operator =(T &)'
1> with
1> [
1> T=int
1> ]
1> while trying to match the argument list 'myreference<int>, int'
I was searching through the web and found similar errors (with different operators) and the result was to put the operator outside of its class, but that is not possible for operator= for some (I believe good) reason. My question is, what is the compiler complaining about? Argument list is myreference<int>, int and I have operator defined for Ts which, in this case, is int.
You instantiated an object of type myreference<int>
myreference<int> myref;
So the assignment operator is specialized for parameter type int &. However you are trying to assogn a temporary object that may be bound to a constant reference. So the assignment operator has to be specialized for parameter type const int &.
Thus you need to define another object of type myreference<const int> that the code would be at least compiled. For example
myreference<const int> myref1;
myref1 = 42;
However in any case the code will have undefined behaviour because the temporary object will be deleted after the executing this assignment statement and data member data will have invalid value.
Usually such claases suppress using temporary objects that to avoid the undefined behaviour.
Your problem is bigger than the compiler error. The compiler error is telling you that you have a fundamental design error.
Your reference = both acts as a reference rebinder (changes what an empty reference is attached to) and an assignment (chanhes the value of the thing attached to). These are fundamantally different operations.
One requires a long-term value you can modify later (rebind), and the other requres a value you can read from (assignment).
However with one function, the parameter passed has to be either -- and those types do not match. A readable from value is a T const&. A bindable value is a T&.
If you change the type to T const& the rebind data = &t fails, as it should. If you pass a temporary, like 42, to your =, a rebind attaches to it, and becomes invalid at the end of the calling line. Similarly if you assign int const foo = 3; to it.
You can 'fix' this with a const_cast, but that just throws the type checking out the window: it hides the undefined behaviour instead of giving you a diagnosis.
The T& parameter, on the other hand, cannot bind to a 42 temporary, nor can it bind to a constant const int foo = 3. Which is great in the rebind case, but makes assignment sort of useless.
If you have two operations, =(T const &) and .rebind(T&), your problem goes away. As a side effect, = when not set is indefined behaviour. But that was basically true before.
Your reference is probably better called a 'pointer' at this point.
myreference<T>& operator=(T& t)
myref = 42;
non-const reference can't be bound to a temporary. You can change it to take parameter by const reference.
myreference<T>& operator=(const T& t)
Also, make changes in your code to make this work.
data = &t; <<< Replace this
data = const_cast<T*>(&t); <<< with this.
Since assignment operator is expecting variable (in this case an integer) you are not allowed to pass it some value(42).
So use an integer like
int x=42;
myref=x;
Happy coding.
Related
I have a class that has both implicit conversion operator() to intrinsic types and the ability to access by a string index operator[] that is used for a settings store. It compiles and works very well in unit tests on gcc 6.3 & MSVC however the class causes some ambiguity warnings on intellisense and clang which is not acceptable for use.
Super slimmed down version:
https://onlinegdb.com/rJ-q7svG8
#include <memory>
#include <unordered_map>
#include <string>
struct Setting
{
int data; // this in reality is a Variant of intrinsic types + std::string
std::unordered_map<std::string, std::shared_ptr<Setting>> children;
template<typename T>
operator T()
{
return data;
}
template<typename T>
Setting & operator=(T val)
{
data = val;
return *this;
}
Setting & operator[](const std::string key)
{
if(children.count(key))
return *(children[key]);
else
{
children[key] = std::shared_ptr<Setting>(new Setting());
return *(children[key]);
}
}
};
Usage:
Setting data;
data["TestNode"] = 4;
data["TestNode"]["SubValue"] = 55;
int x = data["TestNode"];
int y = data["TestNode"]["SubValue"];
std::cout << x <<std::endl;
std::cout << y;
output:
4
55
Error message is as follows:
more than one operator "[]" matches these operands:
built-in operator "integer[pointer-to-object]" function
"Setting::operator[](std::string key)"
operand types are: Setting [ const char [15] ]
I understand why the error/warning exists as it's from the ability to reverse the indexer on an array with the array itself (which by itself is extremely bizarre syntax but makes logical sense with pointer arithmetic).
char* a = "asdf";
char b = a[5];
char c = 5[a];
b == c
I am not sure how to avoid the error message it's presenting while keeping with what I want to accomplish. (implicit assignment & index by string)
Is that possible?
Note: I cannot use C++ features above 11.
The issue is the user-defined implicit conversion function template.
template<typename T>
operator T()
{
return data;
}
When the compiler considers the expression data["TestNode"], some implicit conversions need to take place. The compiler has two options:
Convert the const char [9] to a const std::string and call Setting &Setting::operator[](const std::string)
Convert the Setting to an int and call const char *operator[](int, const char *)
Both options involve an implicit conversion so the compiler can't decide which one is better. The compiler says that the call is ambiguous.
There a few ways to get around this.
Option 1
Eliminate the implicit conversion from const char [9] to std::string. You can do this by making Setting::operator[] a template that accepts a reference to an array of characters (a reference to a string literal).
template <size_t Size>
Setting &operator[](const char (&key)[Size]);
Option 2
Eliminate the implicit conversion from Setting to int. You can do this by marking the user-defined conversion as explicit.
template <typename T>
explicit operator T() const;
This will require you to update the calling code to use direct initialization instead of copy initialization.
int x{data["TestNode"]};
Option 3
Eliminate the implicit conversion from Setting to int. Another way to do this is by removing the user-defined conversion entirely and using a function.
template <typename T>
T get() const;
Obviously, this will also require you to update the calling code.
int x = data["TestNode"].get<int>();
Some other notes
Some things I noticed about the code is that you didn't mark the user-defined conversion as const. If a member function does not modify the object, you should mark it as const to be able to use that function on a constant object. So put const after the parameter list:
template<typename T>
operator T() const {
return data;
}
Another thing I noticed was this:
std::shared_ptr<Setting>(new Setting())
Here you're mentioning Setting twice and doing two memory allocations when you could be doing one. It is preferable for code cleanliness and performance to do this instead:
std::make_shared<Setting>()
One more thing, I don't know enough about your design to make this decision myself but do you really need to use std::shared_ptr? I don't remember the last time I used std::shared_ptr as std::unique_ptr is much more efficient and seems to be enough in most situations. And really, do you need a pointer at all? Is there any reason for using std::shared_ptr<Setting> or std::unique_ptr<Setting> over Setting? Just something to think about.
I have written the following code:
struct Element
{
int value;
};
struct Array
{
operator Element*();
operator const Element*() const;
Element& operator[](const size_t nIndex);
const Element& operator[](const size_t nIndex) const;
};
int main()
{
Array values;
if (values[0].value == 10)
{
}
return 0;
}
This works fine in x64. But in x86 I get a compiler error:
error C2666: 'Array::operator []': 4 overloads have similar conversions
note: could be 'const Element &Array::operator [](const std::size_t) const'
note: or 'Element &Array::operator [](const std::size_t)'
note: while trying to match the argument list '(Array, int)'
error C2228: left of '.value' must have class/struct/union
If I comment out the implicit conversions func, or add the explicit prefix, the code is compilable in x86.
But I can't understand why this code makes trouble.
Why can't the compiler decide to use an implicit conversion first, or array accessor first? I thought operator[] is higher in precedence.
The type of 0 which is an int needs doesn't directly match the type of the arguments to your [] operator and therefore a conversion is needed.
However it does match the built in [] operator for the Element pointer type. Clang gives a more descriptive error message in this case: https://godbolt.org/z/FB3DzG (note I've changed the parameter to int64_t to make this fail on x64).
The compiler has to do one conversion to use your class operator (the index from int to size_t) and one conversion to use the built in operator (from Array to Element*) it is ambiguous which one it should use.
It works on x64 as your class operator still only requires a single conversion for the index but the built in operator needs 2, 1 from Array to Element* and one for the index from int to int64_t, this makes your class operator a better match and is therefore not ambiguous.
The solution is to either make the conversion operator explicit (which is a good idea anyway) or to ensure that the type you are passing as your index matches the type your operator is expecting. In your example case you can simply pass 0U instead of 0.
Can anybody explain why this code compiles:
typedef struct longlong
{
unsigned long low;
long high;
}
longlong;
typedef longlong Foo;
struct FooStruct
{
private:
Foo bar;
public:
void SetBar(Foo m)
{
bar = m;
}
Foo GetBar()
{
return bar;
}
};
int main()
{
FooStruct f;
Foo m1 = { 1,1 };
Foo m2 = { 2,2 };
f.SetBar(m1);
f.GetBar() = m2; // Here I'd expect an error such as
// "error: lvalue required as left operand of assignment"
}
I expected the compilation to fail with error: lvalue required as left operand of assignment on line f.GetBar() = m2; because IMO f.GetBar() is not an l-value, but it compiles seemlessly and f.GetBar() = m2; is a NOP.
On the other hand if I replace the typedef longlong Foo; by typedef long Foo;, the line mentioned before won't compile and I get the expected error.
I came along this issue while refactoring some old code. The code in this question has no purpose other than to illustrate this issue.
This works because longlong is a class, and as such = is longlong::operator =, the implicitly-defined assignment operator. And you can call member functions on temporaries as long as they're not qualified with & (the default operator = is unqualified).
To restrict the assignment operator to lvalues, you can explicitly default it with an additional ref-qualifier:
struct longlong
{
longlong &operator = (longlong const &) & = default;
// ^
// ...
};
Using operators on objects of class type means to call a function (either a member function of the left-hand operand, or a free function taking the left-hand operand as first argument). This is known as operator overloading.
It's fine to call functions on rvalues so there is no compilation error.
When implementing the overloaded assignment operator, you can mark it so that it can not be called on an rvalue, but the designer of whatever class you are using chose not to do that.
f.GetBar() = m2; // Here I'd expect an error such as
// "error: lvalue required as left operand of assignment"
Your expectation is in error. This rule only applies to objects of built-in type.
[C++14: 3.10/5]: An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [ Example: a member function called for an object (9.3) can modify the object. —end example ]
Recall that your = here is actually a call to operator=, which is a function.
Reasons for this behavior were described in other answers.
One way to avoid this behavior would be qualifying your return type with const:
struct FooStruct
{
...
const Foo GetBar() {return bar;}
};
You cannot assign values to const objects, so the compiler will complain.
Any idea why a1 =a2 does not work but a2=a1 works. There must be a function in the smart pointer template that does the conversion? which one is it?
#include "stdafx.h"
#include<memory>
class TestClass
{
public:
int a ;
};
typedef std::shared_ptr<TestClass> TestSP;
typedef std::shared_ptr<const TestClass> TestConstSP;
int _tmain(int argc, _TCHAR* argv[])
{
TestSP a1 = TestSP();
TestConstSP a2 = TestConstSP();
//a1 =a2; //error C2440: '<function-style-cast>' : cannot convert from 'const std::shared_ptr<_Ty>' to 'std::shared_ptr<_Ty>'
a2=a1;
return 0;
}
This is due to the usage of const. Should you have a const pointer 'const_ptr' and a non const pointer 'non_const_ptr', it is OK to do:
const_ptr = non_const_ptr; // const_ptr doesn't allow modifying the pointed value, while non_const_ptr does.
But it is forbidden to do:
non_const_ptr = const_ptr; // const_ptr is `const`. Allowing non_const_ptr to modify the pointed value would'n respect the `const` contract.
And the following would work:
non_const_ptr = (type *) const_ptr; // You have the right to do that as you explicitely break the contract.
// The programmer is the boss. This is a bit ugly though.
The exact same logic applies to your example.
This is specified in an interesting way, and it looks like MSVC implemented the standard to the letter here. The assignment itself is specified in §20.8.2.2.3 [util.smartptr.shared.assign]/p1-3:
shared_ptr& operator=(const shared_ptr& r) noexcept;
template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
template<class Y> shared_ptr& operator=(auto_ptr<Y>&& r);
Effects: Equivalent to shared_ptr(r).swap(*this).
Returns: *this.
[ Note: The use count updates caused by the temporary object construction and destruction are not
observable side effects, so the implementation may meet the effects (and the implied guarantees) via
different means, without creating a temporary. <example omitted>]
The relevant constructor is specified in §20.8.2.2.1 [util.smartptr.shared.const]/p17-19:
shared_ptr(const shared_ptr& r) noexcept;
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
Requires: The second constructor shall not participate in the overload resolution unless Y* is implicitly convertible to T*.
Effects: If r is empty, constructs an empty shared_ptr object; otherwise, constructs a shared_ptr object that shares ownership with
r.
Postconditions: get() == r.get() && use_count() == r.use_count().
Since const TestClass * is not implicitly convertible to TestClass *, the templated constructor does not participate in overload resolution, rendering shared_ptr(r) ill-formed as there is no matching constructor.
Edit: I see the confusion. VS2012 is rather poorly designed in reporting compiler error messages. The full error message emitted by the compiler is:
error C2440: '<function-style-cast>' : cannot convert from 'const std::shared_ptr<_Ty>' to 'std::shared_ptr<_Ty>'
with
[
_Ty=const TestClass
]
and
[
_Ty=TestClass
]
Importantly, the two _Tys in the error output are referring to different types. The Error List window in VS2012, however, truncates this to the first line only, and loses that essential information. You should look at the build output for the full error message.
Given a template pass-by-reference conversion/type-cast operator (without const) is possible:
class TestA
{
public:
//Needs to be a const return
template<typename TemplateItem>
operator TemplateItem&() const {TemplateItem A; A = 10; return A;}
};
int main()
{
TestA A;
{
int N;
N = A;
printf("%d!\n",N);
}
{
float N;
N = A;
printf("%f!\n",N);
}
return 0;
}
And given the following code (with const):
class TestA
{
public:
//Produces error
template<typename TemplateItem>
operator const TemplateItem&() const {TemplateItem A; A = 10; return A;}
};
Produces these errors:
error: cannot convert 'TestA' to 'int' in assignment
error: cannot convert 'TestA' to 'float' in assignment
Question
How do I make it so the conversion/type-cast operator return a const pass-by-reference of the template type?
Context
Before most people come in and freak about how 'you can't convert it to just anything', you'll need context. The above code is pseudo code - I'm only interested on const reference returns being possible, not the pitfalls of a templated conversion function. But if you're wondering what it's for, it's relatively simple:
TemplateClass -> Conversion (turned into byte data) -> File
TemplateClass <- Conversion (changed back from byte data) <- File
The user is expected to know what they are getting out, or it's expected to be automated (I.E. saving/loading states). And yes, there is a universal method for templates using pointers to convert any type into byte data.
And don't give me claptrap about std doing this sort of thing already. The conversion process is part of a more complicated class library setup.
I'm a programmer. Trust me. C++ trusts me and lets me make mistakes. Only way I'll learn.
Firstly, your conversion operator is already undefined behavior because you return a reference (const or not) to a local variable that has gone out of scope. It should work fine if you change your conversion operator to return by value which won't induce UB.
EDIT: (removed incorrect information about conversion operators).
But are you really sure that you really want your class type to be convertible to anything? That seems like it's just going to cause many headaches in the future when you're maintaining the code and it converts to an unexpected type automatically.
Another possible implementation is to create an as template method that basically does what your conversion operator wants to do and call it like obj.as<int>().