Search code examples
c++visual-studiocompiler-errorsmacrosstatic-assert

static_assert inside a macro throwing compile time errors even when it shouldn't (Visual Studio)


tl;dr

When I try to compile this code, Visual Studio throws a compile time error for very static_assert, regardless of wheter it should, and then also one "active error" for the only one that should. This is an issue with my code, or with Visual Studio?

Visual Studio Version
Microsoft Visual Studio Community 2019
Version 16.10.2

#include <memory>

struct Node {};
struct A : Node {};
struct B : Node {};

#define cast_to_A(ptr) ([&] () { \
    static_assert( \
        std::is_same<std::shared_ptr<Node>, decltype(ptr)>::value, \
        "Cannot cast to an A pointer." \
    ); \
    return std::static_pointer_cast<A>(ptr); \
})()

#define cast_to_B(ptr) ([&] () { \
    static_assert( \
        std::is_same<std::shared_ptr<Node>, decltype(ptr)>::value, \
        "Cannot cast to a B pointer." \
    ); \
    return std::static_pointer_cast<B>(ptr); \
})()

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    auto aAsNode = std::static_pointer_cast<Node>(a);
    auto bAsNode = std::static_pointer_cast<Node>(b);

    auto a1 = cast_to_A(aAsNode); // Expected: No error, Actual: "Cannot cast to an A pointer."
    auto b1 = cast_to_B(bAsNode); // Expected: No error, Actual: "Cannot cast to an B pointer."

    auto badCast = cast_to_A(b); // Expected & Actual: "Cannot cast to an A pointer."

    return 0;
}

Context

In a personal project, I have a node struct from which a bunch of child structs inherit. Throughout the program I cast from std::shared_ptr<Node> to std::shared_ptr<Child> and vice-versa using std::static_pointer_cast. Somewhere in my program I have compile time errors where I'm acidentally trying to convert from one sibling pointer to another. However, the error is attriubted to code within std::static_pointer_cast (line 1925 of <memory>), and as it isn't a runtime error I can't move up the call stack to find where my bad call is.

As it happens, the code for my structs is procedurally generated by a python script I wrote. My attempted solution was to generate a macro for each struct (like seen in the toy example), then I just did a regex search/replace to replace all my std::static_pointer_cast calls with the appropriate macro.

The issue I have is that recreated in the toy example above. Visual Studio puts a red line under the macro "calls" that I would expect to fail (so I've actually found the bug now), but for every single use of the macro it also throws a compile time error from the static_assert, even when (to my understanding) it shouldn't fail. It doesn't put a red line under those "calls" though, so I'm unsure if this is a Visual Studio bug.

Looking around online saw that std::is_same can be quite particular about what counts as the same type or not. The variable given as the marco argument get's passed to the static_assert by reference (as it passes through the lambda's [&] capture), so I wondered if this was throwing it off. But I couldn't figure out any way to tweak the types to make it work.

Thanks in advance for any help! Any pointers or tips are much appreciated!


Solution

  • @1201ProgramAlarm answered in the comments. The solution was to use std::remove_reference to remove the reference added to the ptr when it is passed into the lambda. The final macro was:

    #define cast_to_A(ptr) ([&] () { \
        static_assert( \
            std::is_same<std::shared_ptr<Node>, std::remove_reference<decltype(ptr)>::type>::value, \
            "Cannot cast to an A pointer." \
        ); \
        return std::static_pointer_cast<A>(ptr); \
    })()
    

    Now only the expected "bad cast" throws an error