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.
Related
I am trying to write a simple class that will act as a complex<double> number when used in a context that warrants a complex number, otherwise it will act as a double (with the requirement that the imaginary part must be 0 when used this way)
This is what I have so far:
class Sample
{
public:
Sample() {}
Sample(double real) : m_data(real) {}
Sample(double real, double imaginary) : m_data(real, imaginary) {}
operator std::complex<double>() const
{
return m_data;
}
operator double() const
{
assert(m_data.imag() == 0.0);
return m_data.real();
}
private:
std::complex<double> m_data;
};
This works great for most situations. Anytime this class is passed to a function that expects a complex number it will act as though it is a complex number. Anytime it is passed to a function that expects a double it will act as though it is a double.
The problem arises when I pass it to a function that will accept BOTH complex numbers and doubles. (for example std::arg). When I try to pass a Sample object to std::arg it doesn't know which conversion to use since both are technically valid.
In situations like this I want the complex conversion to be "preferred" and have it just pass it as a complex. Is there any way to make one user defined function preferred over another when both conversions would be technically acceptable?
I think in general it is impossible to write one conversion operator that will always be preferred to another conversion operator in the same class.
But if you need just to call std::arg with the argument of your class, then it already behaves as you expect preferring std::complex<double> over double:
#include <complex>
#include <iostream>
struct S {
operator std::complex<double>() const {
std::cout << "std::complex<double>";
return 2;
}
operator double() const {
std::cout << "double";
return 2;
}
};
int main() {
S s;
//(void)std::arg(s); //error: cannot deduce function template parameter
(void)std::arg<double>(s);
}
The program compiles and prints std::complex<double>. Demo: https://gcc.godbolt.org/z/dvxv17vvz
Please note that the call std::arg(s) is impossible because template parameter of std::complex<T> cannot be deduced from s. And std::arg<double>(s) prefers std::complex overload in all tested implementations of the standard library.
I currently have a map<int, std::wstring>, but for flexibility, I want to be able to assign a lambda expression, returning std::wstring as value in the map.
So I created this template class:
template <typename T>
class ValueOrFunction
{
private:
std::function<T()> m_func;
public:
ValueOrFunction() : m_func(std::function<T()>()) {}
ValueOrFunction(std::function<T()> func) : m_func(func) {}
T operator()() { return m_func(); }
ValueOrFunction& operator= (const T& other)
{
m_func = [other]() -> T { return other; };
return *this;
}
};
and use it like:
typedef ValueOrFunction<std::wstring> ConfigurationValue;
std::map<int, ConfigurationValue> mymap;
mymap[123] = ConfigurationValue([]() -> std::wstring { return L"test"; });
mymap[124] = L"blablabla";
std::wcout << mymap[123]().c_str() << std::endl; // outputs "test"
std::wcout << mymap[124]().c_str() << std::endl; // outputs "blablabla"
Now, I don't want to use the constructor for wrapping the lambda, so I decided to add a second assignment operator, this time for the std::function:
ValueOrFunction& operator= (const std::function<T()>& other)
{
m_func = other;
return *this;
}
This is the point where the compiler starts complaining. The line mymap[124] = L"blablabla"; suddenly results in this error:
error C2593: 'operator = is ambiguous'
IntelliSense gives some more info:
more than one operator "=" matches these operands: function
"ValueOrFunction::operator=(const std::function &other) [with
T=std::wstring]" function "ValueOrFunction::operator=(const T
&other) [with T=std::wstring]" operand types are: ConfigurationValue =
const wchar_t
[10] c:\projects\beta\CppTest\CppTest\CppTest.cpp 37 13 CppTest
So, my question is, why isn't the compiler able to distinguish between std::function<T()> and T? And how can I fix this?
The basic problem is that std::function has a greedy implicit constructor that will attempt to convert anything, and only fail to compile in the body. So if you want to overload with it, either no conversion to the alternative can be allowed, of you need to disable stuff that can convert to the alternative from calling the std::function overload.
The easiest technique would be tag dispatching. Make an operator= that is greedy and set up for perfect forwarding, then manually dispatch to an assign method with a tag:
template<typename U>
void operator=(U&&u){
assign(std::forward<U>(u), std::is_convertible<U, std::wstring>());
}
void assign(std::wstring, std::true_type /*assign_to_string*/);
void assign(std::function<blah>, std::false_type /*assign_to_non_string*/);
basically we are doing manual overload resolution.
More advanced techniques: (probably not needed)
Another approach would be to limit the std::function = with SFINAE on the argument being invoked is valid, but that is messier.
If you have multiple different types competing with your std::function you have to sadly manually dispatch all of them. The way to fix that is to test if your type U is callable with nothing and the result convertible to T, then tag dispatch on that. Stick the non-std::function overloads in the alternative branch, and let usual more traditional overloading to occur for everything else.
There is a subtle difference in that a type convertible to both std::wstring and callable returning something convertible to T ends up being dispatched to different overloads than the original simple solution above, because the tests used are not actually mutually exclusive. For full manual emulation of C++ overloading (corrected for std::functions stupidity) you need to make that case ambiguous!
The last advanced thing to do would be to use auto and trailing return types to improve the ability of other code to detect if your = is valid. Personally, I would not do this before C++14 except under duress, unless I was writing some serious library code.
Both std::function and std::wstring have conversion operators that could take the literal wide string you are passing. In both cases the conversions are user defined and thus the conversion sequence takes the same precedence, causing the ambiguity. This is the root cause of the error.
I am using a library that is templated and that I do not wish to modify. Namely CImg.
This library has been mostly designed to work with templates of simple types: float, double, int etc.
At some point, this library does:
CImg<T>& fill(const T val) {
if (is_empty()) return *this;
if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val;
else std::memset(_data,(int)val,size()*sizeof(T));
return *this;
}
Now I want to use this library with a more complex class as template parameter. My particular class is such that sizeof(T)!=1 and most of the time, the fill function will properly assign val to each element with the proper operator= of my class. However, when !val, I would like a conversion operator that allows my class to be cast to an int and to produce some values (for example, 0 would make the function above work).
Right now, my program does not compile as it says:
error C2440: 'type cast' : cannot convert from 'const MyClass' to 'int'
How can I create an operator that allows for (int)my_variable with my_variable of type MyClass to be legal, without modifying the function above ?
Something like this using user defined conversions
int type;
explicit operator int()
{
return type;
}
What you want in this case is probably int conversion operator overload:
class A{
public:
explicit operator int() const{
return 2;
}
};
EDIT:
|I added explicit conversion that should make your code compile (at least the method you showed us), and not mess-up other operators, but it's only allowed since C++11, so if you are using older compiler it might not be available yet.
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>().
I would like to prefer a certain implicit conversion sequence over another. I have the following (greatly simplified) class and functions:
class Whatever {...}
template <class T>
class ref
{
public:
operator T* ()
{
return object;
}
operator T& ()
{
return *object;
}
T* object;
...
};
void f (Whatever*)
{
cout << "f (Whatever*)" << endl;
}
void f (Whatever&)
{
cout << "f (Whatever&") << endl;
}
int main (int argc, char* argv[])
{
ref<Whatever> whatever = ...;
f(whatever);
}
When I have a ref object and I am making an ambiguous call to f, I would like the compiler to choose the one involving T&. But in other unambiguous cases I wish the implicit conversion to remain the same.
So far I have tried introducing an intermediate class which ref is implicitly convertible to, and which has an implicit conversion operator to T*, so the conversion sequence would be longer. Unfortunately it did not recognize in unambiguous cases that it is indeed convertible to T*. Same thing happened when the intermediate class had a(n implicit) constructor. It's no wonder, this version was completely unrelated to ref.
I also tried making one of the implicit conversion operators template, same result.
There's no "ranking" among the two conversions; both are equally good and hence the overload is ambiguous. That's a core part of the language that you cannot change.
However, you can just specify which overload you want by making the conversion explicit:
f((Whatever&) whatever);
Simple: define void f(const ref<Whatever>&), it will trump the others which require a conversion.
Only one user-defined conversion function is applied when performing implicit conversions. If there is no defined conversion function, the compiler does not look for intermediate types into which an object can be converted.