ofstream() '<<' ambiguous error

Pages: 12
Do type traits count?

Specifying type trait will not select that operator:

1
2
3
4
5
6
7
#include <type_traits>

template <class _Ty, std::enable_if_t<std::is_same_v<_Ty, std::ofstream>>>
int_precision&& operator<<(const _Ty& lhs, const int_precision& rhs)
{
	return int_precision(lhs);
}


Which has the same effect as explicit specifier and operator without type traits:

1
2
3
4
5
6
7
8
9
10
11
12
13
class int_precision
{
public:
	explicit int_precision(const std::string& str)
	{
	}
};

template <class _Ty = std::ofstream>
int_precision&& operator<<(const _Ty& lhs, const int_precision& rhs)
{
	return int_precision(lhs);
}


Therefore we either specify explicit or disable converting constructor but only for that operator.
Last edited on
That still doesn't answer @doug4's question of why the setup should lead to ambiguity with std::ofstream but not with std::cout (which I think is a std::basic_ostream).
That's interesting,
the answer lies somewhere here:
https://en.cppreference.com/w/cpp/language/overload_resolution
https://en.cppreference.com/w/cpp/language/adl

One thing that makes a difference is:
1
2
3
4
int_precision operator<<(const std::ostream& lhs, const int_precision& rhs)
{
	return int_precision(rhs.str);
}

vs

1
2
3
4
int_precision operator<<(const std::ofstream& lhs, const int_precision& rhs)
{
	return int_precision(rhs.str);
}


Where first version works and second does not, the only difference is first parameter.

class:
1
2
3
4
5
6
7
8
9
class int_precision
{
public:
	/*explicit*/ int_precision(const std::string& str)
	{
	}

	std::string str;
};
That function template with a return type of int_precision&& returns a dangling reference. It is always undefined behavior to access its result.

Don't return references from functions for no reason, but even if there is a reason, in every case there must be a guarantee that the referent is still within its lifetime when the function returns. Do not return references to local variables and do not return references to temporary objects.

Back on topic, consider this program:
1
2
3
4
5
6
7
void f(long, int) {}
void f(int, long) {}

int main()
{
  f(0, 0);
}


Ultimately this asks the compiler to choose between a call to
void f(int, long)
and a call to
void f(long, int)
where both arguments have type int.
- To call the first overload, the first argument must undergo a type conversion from int to long.
- To call the second overload the second argument must undergo a type conversion from int to long.
The compiler can't choose which argument to convert, so it complains.

Now look at this program which more closely corresponds to the actual case:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ostream {}; 
struct ofstream: ostream {}; 
struct int_precision { int_precision(int) {}; };  

// First overload corresponds to `operator<<` in the standard namespace
void ambiguous(ostream&, int) {}; 

// Second overload corresponds to template `operator<<` in iprecision.h
template <typename T>
  void ambiguous(T&, int_precision);
  
int main()
{
  ostream  cout; 
  ofstream fout;

  ambiguous(fout, 1);   
}

This is asking the compiler to choose between a call to
void ambiguous(ostream&, int);
and
void ambiguous(ofstream&, int_precision);
This second function signature is the candidate function the compiler considers after performing template argument deduction.

Like in the prior example,
- To call the first overload, the first argument must undergo a type conversion from ofstream to ostream, the type of its base class.
- To call the second overload, the second argument must undergo a type conversion from int to int_precision, which is made possible via int_precision's converting constructor.
The compiler still can't choose which argument to convert, so it complains.
Last edited on
Does this mean that in the original problem:
- cout doesn't lead to ambiguity because, as an ostream, it can immediately use the standard stream operator << with arguments (ostream&,string) and no conversion of any type is required, whereas
- fout does lead to ambiguity because it is an ofstream (derived class of an ostream) and so would need type conversion of at least one argument to use either the standard stream operator << with arguments (ostream&,string) or the template operator << with arguments (T&,int_precision)

I can't help feeling that C++ was being a little unkind using the same operator << for both stream insertion and bit shifting (presumably the intention of the original template function in this thread). Not to mention things like vector<vector<int>>.

Implicit type conversion can really sting you sometimes - especially where it is permitted through a constructor like that of int_precision here.
Yes, that's what it is.

I can't help feeling that C++ was being a little unkind using the same operator << for both stream insertion and bit shifting (presumably the intention of the original template function in this thread). Not to mention things like vector<vector<int>>.
As far as I recall from D&E, the dual meaning of operator<< originated in a library early in C++'s development. I think there were few compelling alternatives at that time. But I agree that standard streams don't have a great interface.
Last edited on
Topic archived. No new replies allowed.
Pages: 12