Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Overloading-Problem

15 views
Skip to first unread message

Stefan Reuther

unread,
Jul 19, 2017, 1:00:05 PM7/19/17
to
Hallo,

ich bin heute auf ein mir unerklärliches Overloading-Problem gestoßen.
Minimiert sieht das so aus:
----8<--------8<--------8<--------8<--------8<----
#include <string>

template<typename T>
class foo {
public:
foo()
{ }

foo(const foo& f) //1
{ m = f.m; }

template<typename U>
foo(U&& u) //2
{ m = u; }

private:
T m;
};

void bar() {
foo<std::string> a;
foo<std::string> b(a); //3
}
----8<--------8<--------8<--------8<--------8<----

In Zeile //3 wählt der Compiler (z.B. g++-5.4.0 -std=c++11) den Overload
//2 und beschwert sich dann, dass er das keinen passenden
Zuweisungsoperator für 'm = u' findet.

> t.cpp: In instantiation of ‘foo<T>::foo(U&&) [with U = foo<std::__cxx11::basic_string<char> >&; T = std::__cxx11::basic_string<char>]’:
> t.cpp:22:24: required from here
> t.cpp:14:11: error: no match for ‘operator=’ (operand types are ‘std::__cxx11::basic_string<char>’ and ‘foo<std::__cxx11::basic_string<char> >’)

Erwartet hätte ich, dass Overload //1 gewählt wird. Wenn ich //2
auskommentiere, ist das auch der Fall.

Gewohnt bin ich aus C++98, dass der Compiler gerade fürs Kopieren eines
Objektes einen Template-Konstruktor nicht einmal zur Kenntnis nimmt.

Wie bekomme ich das aufgelöst?


Stefan

Florian Weimer

unread,
Jul 20, 2017, 12:40:05 PM7/20/17
to
* Stefan Reuther:

> Erwartet hätte ich, dass Overload //1 gewählt wird. Wenn ich //2
> auskommentiere, ist das auch der Fall.

Für //1 fehlt aber das const, deswegen gewinnt offenbar //2.

> Gewohnt bin ich aus C++98, dass der Compiler gerade fürs Kopieren eines
> Objektes einen Template-Konstruktor nicht einmal zur Kenntnis nimmt.
>
> Wie bekomme ich das aufgelöst?

Man kann sicherlich etwas mit std::enable_if reißen.

Ich würde vermutlich das Overloading weglassen und die Spezialisierung
für foo mittels einer Trait-struct o.ä. erreichen.

Stefan Reuther

unread,
Jul 21, 2017, 2:40:03 PM7/21/17
to
Am 19.07.2017 um 21:37 schrieb Florian Weimer:
> * Stefan Reuther:
>> Gewohnt bin ich aus C++98, dass der Compiler gerade fürs Kopieren eines
>> Objektes einen Template-Konstruktor nicht einmal zur Kenntnis nimmt.
>>
>> Wie bekomme ich das aufgelöst?
>
> Man kann sicherlich etwas mit std::enable_if reißen.
>
> Ich würde vermutlich das Overloading weglassen und die Spezialisierung
> für foo mittels einer Trait-struct o.ä. erreichen.

Konkret soll das ein std::optional-Ersatz für C++98 bis 14 werden, die
Signaturen der Konstruktoren sind durch C++17 vorgegeben.

Ich hatte gehofft, dass das mit "Signaturen in ein Headerfile kopieren
und die paar nötigen Zeilen Code reinkopieren" abgeht - vor allem ohne
zusätzliche Dependencies. Ich fürchte, ich muss dann doch mal bei Boost
spicken gehen...


Stefan

SG

unread,
Aug 14, 2017, 8:10:04 AM8/14/17
to
Am Mittwoch, 19. Juli 2017 19:00:05 UTC+2 schrieb Stefan Reuther:
>
> template<typename T>
> class foo {
> public:
>
> foo(const foo& f) //1
>
> template<typename U>
> foo(U&& u) //2
>
> };
>
> void bar() {
> foo<std::string> a;
> foo<std::string> b(a); //3
> }
> ----8<--------8<--------8<--------8<--------8<----
>
> In Zeile //3 wählt der Compiler (z.B. g++-5.4.0 -std=c++11) den Overload
> //2 und beschwert sich dann, dass er das keinen passenden
> Zuweisungsoperator für 'm = u' findet.
>
> Erwartet hätte ich, dass Overload //1 gewählt wird. Wenn ich //2
> auskommentiere, ist das auch der Fall.
>
> Gewohnt bin ich aus C++98, dass der Compiler gerade fürs Kopieren eines
> Objektes einen Template-Konstruktor nicht einmal zur Kenntnis nimmt.

Das war aber noch nie so.

> Wie bekomme ich das aufgelöst?

Wegen //3 wird bei //2 der Parameter U mit foo<string>& deduziert und
ist ein besserer Match als //1, weil da bei //1 noch ein const dazu
kommen würde.

Eine Möglichkeit wäre, dem zweiten Konstruktor quasi einen Namen
geben. Das findest Du so so ähnlich auch bei std::pair. Das Ding hat
nämlich folgenden Konstruktor ab C++11:

template< class... Args1, class... Args2 >
pair( std::piecewise_construct_t,
std::tuple<Args1...> first_args,
std::tuple<Args2...> second_args );

Aufrufen tut man den dann so:

pair<T,U> x (std::piecewise_construct,
std::forward_as_tuple(...),
std::forward_as_tuple(...) );

Der erste "dummy" Parameter ist quasi der Name des Konstruktors.

Du kannst aber auch sowas schreiben, wenn Du magst:

template<typename U
,class = typename std::enable_if<
std::is_constructible<T,U&&>::value
>::type
>
foo(U&& u)
: m(std::forward<U>(u))
{}

(Bei dem type-trait bin ich mir gerade nicht sicher. Habe ich
jetzt nicht extra nachgeguckt)

SG

unread,
Aug 14, 2017, 8:10:04 AM8/14/17
to
Am Freitag, 21. Juli 2017 20:40:03 UTC+2 schrieb Stefan Reuther:
>
> Konkret soll das ein std::optional-Ersatz für C++98 bis 14 werden, die
> Signaturen der Konstruktoren sind durch C++17 vorgegeben.
>
> Ich hatte gehofft, dass das mit "Signaturen in ein Headerfile kopieren
> und die paar nötigen Zeilen Code reinkopieren" abgeht - vor allem ohne
> zusätzliche Dependencies. Ich fürchte, ich muss dann doch mal bei Boost
> spicken gehen...

Du hast eine Bemerkung übersehen:

template<class U = T> EXPLICIT constexpr optional(U&& v);

[...] This constructor shall not participate in overload resolution
unless is_constructible_v<T,U&&> is true, is_same_v<U, in_place_t>
is false, and is_same_v<optional<T>, decay_t<U>> is false. [...]

Das "shall not participate in overload resolution" wird üblicherweise
via SFINAE implementiert, also etwa so:

template<class U = T
,class = typename std::enable_if<
std::is_constructible<T,U&&>::value &&
!std::is_same<U, in_place_t>::value &&
!std::is_same<optional<T>,
typename std::decay<U>::type >::value
>::type
> constexpr optional(U&& v)
[...]

Diese "Forwarding-Referenz" zusammen mit den type traits gibt's
natürlich erst ab C++11.

SG
0 new messages