2

I'm trying to detect when an explicit constructor call has been called vs. an implicit one.

Suppose we have a class Foo:

class Foo{
public:
    Foo(int _val) : val(_val){};
private:
    int val;
}

void bar(Foo f){
   ...
}

We can call bar like:

Foo f(10);
bar(f);

or like:

bar(10); // implicit initialization

Now, I am aware that if we make the ctor explicit:

class Foo{
public:
    explicit Foo(int _val) : val(_val){};
private:
    int val;
}

Then we can get this error:

bar(10); // ERROR! implicit initialization not allowed.

So I thought perhaps there could be a workaround to detect an explicit call vs. implicit, like this:

class Foo{
public:
    explicit Foo(int _val) : val(_val){}; // calling Foo f(10); bar(f);
    Foo(int _val) : val(_val){}; // calling bar(10);
private:
    int val;
}

But as expected, it returns "cannot be overloaded", as the function signatures are ambiguous.

The end result should be something like:

class Foo{
public:
    explicit Foo(int _val) : val(_val), flag(true) {}; // calling Foo f(10); bar(f);
    Foo(int _val) : val(_val), flag(false) {}; // calling bar(10);
private:
    int val;
    bool flag;
}

void bar(Foo f){
    std::cout << "f's flag set to : " << f.flag << std::endl;
}

Foo f(10);
bar(f); // f's flag set to : 1
bar(10); // f's flag set to : 0

But obviously since the above attempt was futile, and I don't have a better idea, I was wondering if it is even possible to do this in C++. If it's not, then it's fine.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
OneRaynyDay
  • 3,658
  • 2
  • 23
  • 56
  • 5
    Why do you think you need to do this? –  Sep 15 '17 at 17:21
  • 8
    This sounds very much like an [XY problem](https://meta.stackexchange.com/a/233676/218910). If you need to detect this, something about your model is very wrong. – cdhowie Sep 15 '17 at 17:21
  • I am not asking whether this should be done, I am asking whether it can be done. This isn't a necessary path I need to take to get where I want. – OneRaynyDay Sep 15 '17 at 17:22
  • in **your current** case, you can differentiate, as explicit constructor would also do a copy, whereas the implicit doesn't do extra copy. – Jarod42 Sep 15 '17 at 17:25
  • BTW, in your comment, "flag set to 1" is misplaced, it should be for `f(10)`. – Jarod42 Sep 15 '17 at 17:26
  • @Jarod42 Not for `bar(Foo(10))` though -- the compiler can elide that copy, which would give a false negative. Since copy-ctor invocations are allowed to be elided, *they should not have side-effects.* – cdhowie Sep 15 '17 at 17:27
  • @Jarod42 Unless I'm missing something, I wanted the flag to be `true` if the explicit constructor is called. `f(10)` implicitly calls the constructor. – OneRaynyDay Sep 15 '17 at 17:29
  • @RSahu that's all I needed. Thanks :) Feel free to post an answer here and I'll accept it. – OneRaynyDay Sep 15 '17 at 17:39
  • What is the actual problem that you need to solve? – Pete Becker Sep 15 '17 at 18:01
  • @PeteBecker it's a little bit involved, but a slight added level of detail: `operator+(Foo foo, Foo foo)`. Predefined `Foo`'s we can inspect, but in `foo + 5`, we'll never really capture the rvalue foo(5) that's generated. Because we never capture that rvalue, we can do some optimizations on it, roughly speaking. I'm not sure if that made it more confusing or less, but we can discuss if you link a chat room. – OneRaynyDay Sep 15 '17 at 18:03
  • @OneRaynyDay I’m not sure I understand what you mean, but it sounds like Pete’s answer is what you actually want, or possibly adding an overload for `operator+(Foo, int)`. – Daniel H Sep 15 '17 at 18:14
  • @DanielH Yeah, but this SO question was specifically for asking whether I can tell between implicit/explicit. I accepted the answer to that question. I had some alternatives in mind like Pete's answer. `operator+(Foo, int)` doesn't work purely because I want to support any numeric type + external libraries like BLAS matrices or smth similar -> implicit cast to Foo. – OneRaynyDay Sep 15 '17 at 18:16
  • @OneRaynyDay Yeah, I figured `int` was probably a simplification, which is why I had the “possibly” there. I’m actually a bit surprised the consensus is that this isn’t possible with some preferred overload magic. – Daniel H Sep 15 '17 at 18:21

3 Answers3

3

it is even possible to do this in C++?

No.

As you saw the overloading ambiquitity is an issue, which makes it impossible for you to know whether the implicit constructor was called or the explicit one.

One may assume that in your case it would be possible to know which constructor was called, by keeping an eye out for the copy needed by the explicit constructor (in contrast with the implicit one), but this is not that much reliable, since a good compiler can take advantage of Copy Elision and bypass the copy operation.

So if we would rely on that copy assumption by us to determine whether the explicit constructor was called or not, we could be receive a false negative, in case the copy was actually elided.

In general, copy constructors are allowed to be elided, they should not have side effects.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
2

So I thought perhaps there could be a workaround to detect an explicit call vs. implicit, like this:

No, it's not possible. If this is purely out of curiosity, then you have your answer. If there is a real problem you are trying overcome, you may want to post the real problem.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

It's easy, but the solution is in the function being called, not in the object that you're creating.

void bar(Foo&) { ... }
void bar(Foo&&) { ... }

Foo f(10);
bar(f);  // calls the first one
bar(10); // calls the second one
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • `bar(Foo(42));` calls the second one. – Jarod42 Sep 15 '17 at 18:06
  • @Jarod42 -- yes, but that's not one of the examples in the question. It's an inherently fuzzy question, since its real goal is not stated. One of the comments suggests that the actual goal is capturing rvalue-ness, not implicit construction, in which case this approach does address the issue. – Pete Becker Sep 15 '17 at 18:08
  • 1
    Actually, good answer as well; it's funny because this was my "backup plan". I was going to overload the functions with rvalue and references. However, this does have the drawback of having 2^n many overloads where n is the # of inputs of Foo. For normal binary/unary operators, not too terrible. Upvoted for reading my mind – OneRaynyDay Sep 15 '17 at 18:09
  • @OneRaynyDay -- indeed; we overaload on rvalues vs. lvalues all through the standard library. – Pete Becker Sep 15 '17 at 18:10
  • @PeteBecker Woah, you wrote the standard library? Honored to be in your presence :P – OneRaynyDay Sep 15 '17 at 18:12
  • @OneRaynyDay With C++2a’s concepts, or some template metaprogramming magic, you don’t need 2^n possibilities. Then again, if you want to do optimizations, you might actually have 2^n different cases, so perhaps you should. – Daniel H Sep 15 '17 at 18:17
  • @OneRaynyDay -- I'm not sure how to take that comment. I was project editor for C++11, and I've implemented much of the C++ standard library for Borland International and for Dinkumware, Ltd. I've been deeply involved in the design, specification, and implementation of the C++ standard library for over twenty years. – Pete Becker Sep 15 '17 at 18:18
  • @DanielH ++2a? We just got started on ++17 o_O and Pete, it wasn't sarcasm if that's what you were assuming – OneRaynyDay Sep 15 '17 at 18:22
  • @OneRaynyDay The standards committee is already working on 2a, and GCC has an implementation of concepts already if you turn on the right compiler flag. Without that, you need `template >, std::is_same>> Foo operator+(LHS const&& lhs, RHS const&& rhs)` (untested), which will bind to any sort of `Foo`, and you can tell what’s an rvalue reference by whether `LHS` and `RHS` are `Foo&` (lvalue) or `Foo` (rvalue). – Daniel H Sep 15 '17 at 18:30
  • @DanielH that's so ugly agh, but I see it does the job :P – OneRaynyDay Sep 15 '17 at 18:38
  • @OneRaynyDay It could be improved somewhat by using [`std::is_convertible`](http://en.cppreference.com/w/cpp/types/is_convertible), which also lets you get rid of the `remove_reference_t`s. And it should be `enable_if_t` and `conjunction_t`. At that point, it’s `template , std::is_convertible>>>`, which is almost readable, but I still can’t wait for concepts to make it actually readable. – Daniel H Sep 15 '17 at 19:33
  • (If you go with something like that, you should also declare it in `Foo`’s namespace so ADL doesn’t find it unless one of the arguments is a `Foo`, or declare it as a friend inside `Foo` class if it needs friendship.) – Daniel H Sep 15 '17 at 19:35