Avoid indirect instantiation from private constructor through operation

  • A+

I am trying to create a class whose objects must contain a short description ("name") of what their value represent. Therefore the only public constructor should take a string as argument.

For the operations, however, I need to create temporary (no relevant name) object to calculate the value to be assigned to an already existing object. For that I have implemented a private constructor, which should not be used, neither directly nor indirectly, to instantiate a new object - these temporary objects should only be assigned to an already existing object, through operator=, which only copies the value rather than name and value.

The problem comes with the use of "auto". If a new variable is declared as follows:

auto newObj = obj + obj; 

the compiler deduces the return type of operator+ and directly assign its result to newObj. This results in an object with a irrelevant name, which should not be possible to instantiate.

Also, deducing the type of an already existing object should still be possible from some functions, like:

auto newObj = obj.makeNewObjWithSameTypeButOtherName("Other name"); 

Follows a code demonstrating the problem:

#include <iostream> #include <string>  using namespace std;  template<class T> class Sample {     public:     Sample(const string&);      Sample<T> makeNewObj(const string&);     // Invalid constructors     Sample();     Sample(const Sample&);      void operator=(const Sample&);     void operator=(const T&);      Sample<T> operator+(const Sample&) const;      void show(void);  private: // Private constructor used during operations Sample(const T&);  T _value; string _name;   };  template<class T> Sample<T>::Sample(const string& name) {     this->_name = name;     this->_value = 0; }  template<class T> Sample<T>::Sample(const T&value) {     this->_name = "Temporary variable";     this->_value = value; }  template<class T> Sample<T> Sample<T>::makeNewObj(const string& name) {     return Sample<T>(name); }  template<class T> void Sample<T>::operator=(const Sample& si) {     this->_name = this->_name; // Make explicit: Never change the name     this->_value = si._value; }  template<class T> void Sample<T>::operator=(const T& value) {     this->_name = this->_name; // Make explicit: Never change the name     this->_value = value; }  template<class T> Sample<T> Sample<T>::operator+(const Sample& si) const {     // if any of the two values are invalid, throw some error     return Sample<T>( this->_value + si._value ); }  template<class T> void Sample<T>::show(void) {     cout << _name << " = " << _value << endl; }  int main() {     Sample<double> a("a"), b("b");     a = 1; // Sample::operator=(const T&)     b = 2.2; // Sample::operator=(const T&)     a.show(); // Output: a = 1     b.show(); // Output: b = 2.2      auto c = a.makeNewObj("c"); // Should be possible     c = a + b; // Sample::operator+(const Sample&) and Sample::operator=(const Sample&)     c.show(); // Output: c = 3.2  //    Sample<double> d; // Compiler error as expected: undefined reference to `Sample::Sample()' //    auto f = a; // Compiler error as expected: undefined reference to `Sample::Sample(Sample const&)'      // This is what I want to avoid - should result in compiler error     auto g = a+c; // No compiler error: uses the private constructor     Sample::Sample(const T&)     g.show(); // Output: Temporary variable = 4.2  <-- !! Object with irrelevant name } 


Somewhat related but also orthogonal to NathanOliver's answer:

You are mixing different concepts here. You have the notion of, essentially, NamedValue with Sample, but you are trying to make each expression formed out of arithmetics on NamedValue also a NamedValue. That is not going to work - the expression (by your semantics) does not have a name, so it should not be a NamedValue. Therefore, having NamedValue operator+(const NamedValue& other) is not meaningful.

Nathan's answer resolves this by making additions return T instead. That's pretty straightforward.

However, note that since a + b must have a type, you cannot stop auto g = a + b from compiling, even if it is demonstrably incorrect code. Ask Eigen, or any other expression template library. This remains true no matter how you choose the return type of operator+. So this wish of yours cannot be granted, unfortunately.

Still, I would suggest that you don't use plain T as return type but rather another class, say, Unnamed<T>:

template<class T> class Unnamed { public:     explicit Unnamed(const T& value) : _value(value) {};      Unnamed<T> operator+(const Unnamed<T>& rhs) const     {         return Unnamed<T>(_value + rhs._value);     }      friend Unnamed operator+(const Unnamed& lhs, const Sample<T>& rhs);     friend Unnamed operator+(const Sample<T>& lhs, const Unnamed& rhs);  private:     T _value; }; 

This allows you to do your checks and what have you on every operation (because the middle + in (a + b) + (c + d) cannot accept NamedValues, see above) instead of only when converting back to a named value.

Demo here.

You can increase the compile-time safety slightly by only allowing construction of Sample from Unnamed temporaries: https://godbolt.org/g/Lpz1m5

This could all be done more elegantly than sketched here. Note that this is moving exactly in the direction of expression templates though.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: