Can operands to `a?b:c` have different types? What's the best alternative?

Viewed 145
#include <iostream>
#include <string>


struct Base
{
    Base(int)
    {
        std::cout << "int\n";
    }

    Base(std::string)
    {
        std::cout << "string\n";
    }
};


struct S : Base
{
    S(bool b)
    : Base{ b ? int{} : std::string{} }
    {}
};


int main()
{
    S(42);
    S("fortytwo");
}

There are are a couple of compilation errors with this code but the more interesting is

operands to ?: have different types: int and std::string.

Constraints:

  • Base ctor initializes constants so I can't use the ctor body.
  • This is the only derived class doing the bool magic, so changing Base to accommodate it would be ... undesirable.

What now? The point of this code is for the derived class S call one of the overloaded ctors of Base based on a bool.

Considered solutions:

  • An IIFE could possibly work but be rather ugly and long.
  • A helper function - same.
  • Templating S and passing b there - I need a runtime decision.

EDIT: @Yakk - Adam Nevraumont's answer is super awesome to the original question.

But I had missed a crucial detail. Base is abstract i.e. add virtual void foo() = 0; to it's definition. So it cannot be instantiated.

2 Answers
S(bool b):
  Base{ b ? Base{int{}} : Base{std::string{}} }
{}

Live example.

You can get fancier:

S(bool b):
  Base{ [&]()->Base{
    if (b)
      return int{};
    else
      return std::string{};
  }() }
{}

Live example.

In the first example, a move is done; in the second, guaranteed elision means only one Base object is created. You can test this by adding:

Base(Base&&)=delete;

As stated in the comments, you can't do this with a constructor (with Base abstract!) because there's no way to wrap a conditional around the invocation of the base constructor. You can, however, do something similar with a factory function:

struct S : Base {
  static S make(bool b) {return b ? S(0) : S("");}
private:
  S(int i) : Base(i) {}
  S(std::string s) : Base(std::move(s)) {}
};

If it happens that sometimes you know the bool at compile time, you might consider instead writing

struct S : Base {
  template<bool> struct tag {};
  S(tag<true>) : Base(i) {}
  S(tag<false>) : Base(std::move(s)) {}

  static S make(bool b) {return b ? S(tag<true>()) : S(tag<false>());}
};
bool f();
S i(S::tag<true>),s(S::tag<false>),unknown=S::make(f());
Related