[C++] template constructor "overschrijft" andere constructor

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#import <iostream>
#import <sstream>
#import <functional>

typedef std::function<void(std::ostringstream &)> foo_handler;

class foo {
public:
  foo(const foo_handler & handler) {
    handler(content_);
  }

  template<class T> foo(const T & fragment) {
    content_ << fragment;
  }

  const std::string str() const {
    return content_.str();
  }
private:
  std::ostringstream content_;
};

void foo_a() {
  foo a([] (std::ostringstream & out) {
    out << "A says: Hello, world!";
  });

  std::cout << a.str() << std::endl;
}

void foo_b() {
  foo_handler handler([] (std::ostringstream & out) {
    out << "B says: Hello, world!";
  });

  foo b(handler);

  std::cout << b.str() << std::endl;
}

int main() {
  foo_a();
  foo_b();
}


Mijn output:
1
B says: Hello, world!


Verwachte output:
A says: Hello, world!
B says: Hello, world!


Commandline
$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix
$ g++ -o sample -Wall -Wextra -std=c++11 -Ofast sample.cpp && ./sample
1
B says: Hello, world!
$


Waarom werkt foo_b wel goed en foo_a niet? Ik wil graag de lambda direct kunnen meegeven bij de constructor van foo. Ik verwacht dat er hier een soort implicit typecast gaande is waarbij de ene keer voorkeur is voor de "eerste" constructor en bij de andere keer voor de templated-constructor.

Wat moet ik doen om de code binnen foo_a te laten werken zonder de code binnen foo_a significant te veranderen?

[ Voor 14% gewijzigd door Gamebuster op 04-05-2015 20:46 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • NESFreak
  • Registratie: December 2009
  • Laatst online: 00:25
Al geprobeerd met template specialisatie?

code:
1
2
3
4
Template <>
foo(const foo_handler & handler) {
    handler(content_);
  }

Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
Toevallig wel. Ik krijg dan deze foutmelding:

code:
1
2
3
4
5
6
7
8
sample.cpp:14:3: error: explicit specialization of 'foo' in class scope
  foo(const foo_handler & handler) {
  ^
sample.cpp:10:14: error: invalid operands to binary expression ('std::ostringstream' (aka 'basic_ostringstream<char>')
      and 'const std::__1::function<void (std::__1::basic_ostringstream<char, std::__1::char_traits<char>,
      std::__1::allocator<char> > &)>')
    content_ << fragment;
    ~~~~~~~~ ^  ~~~~~~~~


Code na je suggestie:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <iostream>
#import <sstream>
#import <functional>

typedef std::function<void(std::ostringstream &)> foo_handler;

class foo {
public:
  template<class T> foo(const T & fragment) {
    content_ << fragment;
  }

  template <>
  foo(const foo_handler & handler) {
    handler(content_);
  }

  const std::string str() const {
    return content_.str();
  }
private:
  std::ostringstream content_;
};

void foo_a() {
  foo a([] (std::ostringstream & out) {
    out << "A says: Hello, world!";
  });

  std::cout << a.str() << std::endl;
}

void foo_b() {
  foo_handler handler([] (std::ostringstream & out) {
    out << "B says: Hello, world!";
  });

  foo b(handler);

  std::cout << b.str() << std::endl;
}

int main() {
  foo_a();
  foo_b();
}


__EDIT__

Ik heb dit gevonden:
http://stackoverflow.com/a/6928769

Maar ik krijg het niet voor elkaar om te compilen en ik heb geen idee wtf er gedaan wordt :P

[ Voor 51% gewijzigd door Gamebuster op 04-05-2015 21:21 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
En het is me gelukt. Ik voel me nu ontzettend vies.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#import <iostream>
#import <sstream>
#import <functional>
#import <type_traits>

typedef std::function<void(std::ostringstream &)> foo_handler;

class foo {
public:
  template<typename F>
  foo(const F & fn, typename std::enable_if<std::is_convertible<F, foo_handler>::value>::type* = 0) {
    fn(content_);
  }

  template<typename T>
  foo(const T & fragment, typename std::enable_if<!std::is_convertible<T, foo_handler>::value>::type* = 0) {
    content_ << fragment;
  }

  const std::string str() const {
    return content_.str();
  }
private:
  std::ostringstream content_;
};

int main() {
  int i = 1337;
  std::cout << foo([&] (std::ostringstream & out) {
    out << "My favorite number: " << i;
  }).str() << std::endl;

  std::cout << foo("I work as well!").str() << std::endl;
}


$ g++ -o sample -Wall -Wextra -std=c++11 -Ofast sample.cpp && ./sample
My favorite number: 1337
I work as well!


...maar als ik erover nadenk is het ergens ook wel enigszins logisch.

[ Voor 9% gewijzigd door Gamebuster op 04-05-2015 21:45 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 01:41
Btw, je had de eerste constructor niet hoeven veranderen. ;)

Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
Bedankt! :D

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#import <iostream>
#import <sstream>
#import <functional>
#import <type_traits>

typedef std::function<void(std::ostringstream &)> foo_handler;

class foo {
public:
  foo(const foo_handler & fn) {
    fn(content_);
  }

  template<typename T>
  foo(const T & fragment, typename std::enable_if<!std::is_convertible<T, foo_handler>::value>::type* = 0) {
    content_ << fragment;
  }

  const std::string str() const {
    return content_.str();
  }
private:
  std::ostringstream content_;
};

int main() {
  int i = 1337;
  std::cout << foo([&] (std::ostringstream & out) {
    out << "My favorite number: " << i;
  }).str() << std::endl;

  std::cout << foo("I work as well!").str() << std::endl;
}

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 01:41
Deze variant heeft ook het voordeel dat het argument daadwerkelijk naar een std::function geconverteerd wordt. In de eerdere variant callde je direct het argument, terwijl dat niet per se mogelijk is.

Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
Aah, omdat-ie wel "converteerbaar" was, maar niet per se uitvoerbaar?

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 01:41
Ja precies, bijvoorbeeld bij zo'n soort type:
C++:
1
2
3
4
5
struct Bla {
  operator foo_handler() const {
    return [](std::ostream& os) { os << "Bla"; };
  }
};

Beetje vergezocht, natuurlijk, maar toch.

[ Voor 4% gewijzigd door Soultaker op 04-05-2015 21:53 ]


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
operator <typename>() const is voor een implicit conversie naar een ander type? Weer wat geleerd :P

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 01:41
Yep.

Ik zou je wel afraden om te veel van dit soort impliciete conversies te definiëren, want dat maakt het erg lastig om bij het lezen van de code te zien welke code er nu precies uitgevoerd gaat worden, en het kan tot rare bugs leiden als types via een impliciete conversie gebruikt kunnen worden op plekken waar je het niet verwacht.

Maar met mate kan het zeker nuttig zijn; zie bijvoorbeeld operator bool() op std::unique_ptr.

[ Voor 13% gewijzigd door Soultaker op 04-05-2015 21:59 ]


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
Duidelijk, bedankt :)

Let op: Mijn post bevat meningen, aannames of onwaarheden


Acties:
  • 0 Henk 'm!

  • NESFreak
  • Registratie: December 2009
  • Laatst online: 00:25
Gamebuster schreef op maandag 04 mei 2015 @ 21:43:
En het is me gelukt. Ik voel me nu ontzettend vies.

C++:
1

...maar als ik erover nadenk is het ergens ook wel enigszins logisch.
Oeh type traits zijn vrij next level. Een 2e argument met een default value (void* = NULL) dat je niet gebruikt dat alleen kan bestaan indien het eerste argument niet naar het andere type convergeert kan worden is idd een oplossing. Helaas niet eentje die ik zelf zomaar zou verzinnen.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:34

.oisyn

Moderator Devschuur®

Demotivational Speaker

Soultaker schreef op maandag 04 mei 2015 @ 21:59:
Maar met mate kan het zeker nuttig zijn; zie bijvoorbeeld operator bool() op std::unique_ptr.
Maar die is dan ook explicit (wat pas ondersteund wordt vanaf C++11). Zonder explicit wil je operator bool() beter vermijden, en krijg je van die verkrachtingen zoals std::basic_ios<>::operator void*(), zodat je niet per ongeluk kunt converten naar int met alle arithmetic operators als bonus.

While on the subject, waarschijnlijk wil je je foo ctors ook explicit maken :). Nu is elk wikkekeurig type converteerbaar naar foo.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Gamebuster schreef op maandag 04 mei 2015 @ 20:39:
[code=c++]
Waarom werkt foo_b wel goed en foo_a niet?
Omdat de vraag nog niet expliciet beantwoord is: de template ctor is een betere match, want geen user-defined conversion. Lambda's hebben allemaal een uniek type, en dat type is niet std::function.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • Gamebuster
  • Registratie: Juli 2007
  • Laatst online: 27-09 22:01
Ik kwam er inderdaad achter dat een lambda een of ander intern uniek type die naar een std::function geconverteerd kan worden. Met <typeinfo> utilities kwam ik daar achter :)

N.a.v. .oisyn's opmerking ga ik uitzoeken wat "explicit" doet en waarom het beter zou zijn in foo's situatie

[ Voor 9% gewijzigd door Gamebuster op 05-05-2015 12:24 ]

Let op: Mijn post bevat meningen, aannames of onwaarheden

Pagina: 1