3

I'm seeing some odd behavior in regard to passing a pointer into a function that expects a constant reference. Obviously C++ expects me to de reference the pointer before I pass it into the function expecting the reference. I'm looking for an error or warning but I'm not getting one UNLESS a certain condition is met that doesn't make any sense to me.

The type of the pointer and reference is a C++ 17 Standard Variant (below). If my C++17 variant includes the boolean in the template (as below) then GCC8.3 compiles the code just fine. But I have unexpected runtime results.

If I remove the boolean from the standard variant template, then the code does not compile as I expect. Why the difference?

#include <iostream>
#include <variant>
#include <stdint.h>

typedef std::variant <
        std::monostate,
        int8_t,
        int16_t,
        int32_t,
        int64_t,
        uint8_t,
        uint16_t,
        uint32_t,
        uint64_t,
        float,
        double
        ,bool //If bool is in the variant, the compiler compiles the code without error and understandably has UB. Why isn't the lack of de-reference bug in DoThing not caught when that is the case?
> StdVARIANT;

//if 'bool' is commented out of the variant the error (expected) is:
//invalid initialization of reference of type ‘const StdVARIANT&’

static size_t GetIndexOfVariant(const StdVARIANT& RefToVariant);
static size_t DoThing(StdVARIANT* PtrToVariant);

static size_t DoThing(StdVARIANT* PtrToVariant){
    return GetIndexOfVariant(PtrToVariant); //this is a bug-PtrToVariant should be de referenced!
}

static size_t GetIndexOfVariant(const StdVARIANT& RefToVariant){
    size_t Index = RefToVariant.index();
    return Index;
}

int main()
{
    StdVARIANT* myvar = new StdVARIANT();
    *myvar = (int32_t)1;
    std::cout<<"long: "<<std::get<int32_t>(*myvar)<<" "<<sizeof(std::get<int32_t>(*myvar))<<std::endl;
    *myvar = (double)2;
    std::cout<<"double: "<<std::get<double>(*myvar)<<" "<<sizeof(std::get<double>(*myvar))<<std::endl;
    std::cout<< myvar->index() << " " << DoThing(myvar)<<std::endl; //when 'bool' is in the std::variant, these two calls return different results (UB)
    delete myvar;
return 0;
}

Compile and run as the code block above:

~/gcc-test$ g++ -std=c++17 -Wall -Wextra -pedantic main.cpp
~/gcc-test$ ./a.out
long: 1 4
double: 2 8
10 11

Comment 'bool' out of StdVARIANT, and:

~/gcc-test$ g++ -std=c++17 -Wall -Wextra -pedantic main.cpp
main.cpp: In function ‘size_t DoThing(StdVARIANT*)’:
main.cpp:27:30: error: invalid initialization of reference of type ‘const StdVARIANT&’ {aka ‘const std::variant<std::monostate, signed char, short int, int, long int, unsigned char, short unsigned int, unsigned int, long unsigned int, float, double>&’} from expression of type ‘StdVARIANT*’ {aka ‘std::variant<std::monostate, signed char, short int, int, long int, unsigned char, short unsigned int, unsigned int, long unsigned int, float, double>*’}
     return GetIndexOfVariant(PtrToVariant); //this is a bug-PtrToVariant should be de referenced!
                              ^~~~~~~~~~~~
main.cpp:23:15: note: in passing argument 1 of ‘size_t GetIndexOfVariant(const StdVARIANT&)’
 static size_t GetIndexOfVariant(const StdVARIANT& RefToVariant);
               ^~~~~~~~~~~~~~~~~

Why don't I always get the error instead of just when bool is commented out?

Timothy John Laird
  • 1,101
  • 2
  • 13
  • 24
  • 2
    Pointers can be implicitly converted to `bool` - it is common, for example, to abbreviate `if (p != nullptr)...` to just `if (p) ...` See: [Why is there an implicit type conversion from pointers to bool in C++?](https://stackoverflow.com/q/4111495/10871073) – Adrian Mole Aug 13 '21 at 21:21
  • @PaulSanders testing with WandBox the bug persists until I try GCC10.1 then it goes away and the compiler throws an error as it should! This helps. The clang++ that ships with debian 10 manifests the same problem as G++. – Timothy John Laird Aug 13 '21 at 21:24
  • Although, having said that, MSVC does at least give a warning (clang-cl doesn't): *warning C4800: Implicit conversion from 'StdVARIANT *' to bool. Possible information loss* – Adrian Mole Aug 13 '21 at 21:25
  • 1
    Is this a duplicate: [What is the best way to disable implicit conversion from pointer types to bool when constructing an std::variant?](https://stackoverflow.com/q/64249832/10871073) Looks like the exact same problem to me, but I'm willing to hold off for a bit before I hammer it closed. Comments welcome... – Adrian Mole Aug 13 '21 at 21:27
  • What Standard (C++20 or C++17) are you using with GCC10.1? – Adrian Mole Aug 13 '21 at 21:29
  • @AdrianMole I'm using GCC8.3 passing option for C++17. The WandBox toy seems to be an objective way to test. And to your other comment - you may consider this a duplicate if you wish. I did not see it because I did not know what to look for. I am sorry. – Timothy John Laird Aug 13 '21 at 21:34
  • No need for apologies - it took me a bit of subtle searching to find the (second) duplicate suggestion. (I was actually looking for some backup in writing an answer.) ... However, I feel that that second post really does answer your question, if indirectly. – Adrian Mole Aug 13 '21 at 21:37

1 Answers1

3

To reproduce the problem, you can simplify your code as follows

#include <iostream>
#include <variant>
#include <stdint.h>

typedef std::variant <
        std::monostate,
        int8_t,
        int16_t,
        int32_t,
        int64_t,
        uint8_t,
        uint16_t,
        uint32_t,
        uint64_t,
        float,
        double
        ,bool //If bool is in the variant, the compiler compiles the code without error and understandably has UB. Why isn't the lack of de-reference bug in DoThing not caught when that is the case?
> StdVARIANT;

int main()
 {
   StdVARIANT * pVar = new StdVARIANT();
   StdVARIANT   var = pVar;
 }

The point is that a StdVARIANT * can't be converted to any of the StdVARIANT alternative except for bool.

So if StdVARIANT contains bool,

   StdVARIANT   var = pVar;

var is initialized as containing a bool value (a true, given that pVar isn't nullptr).

If StdVARIANT doesn't contains bool, the var initialization fails.

The same inside your DoThing() function: when you call

GetIndexOfVariant(PtrToVariant);

a temporary StdVARIANT is created (or not, if bool isn't available) and the constant reference is passed to GetIndexOfVariant().

max66
  • 65,235
  • 10
  • 71
  • 111