Skip to content

Feature: Support [variant] and [hasvariant] attributes #1524

@oldnewthing

Description

@oldnewthing

Version

2.0.250303.1

Summary

The [variant] and [hasvariant] parameter attributes are hints that scalars should be auto-boxed. C++/WinRT does not take advantage of this right now.

Reproducible example

// Sample.idl
namespace Sample
{
    runtimeclass Thing
    {
        static Accept([variant] Object value);
    }
}

// C++/WinRT without [variant] support
winrt::Thing::Accept(winrt::box_value(3));

// C++/WinRT with [variant] support
winrt::Thing::Accept(3);

Expected behavior

The value "3" to be accepted without boxing.

Actual behavior

Compiler error saying that 3 cannot be converted to IInspectable const&.

Additional comments

Sketch is to create a new parameter type winrt::param::variant.

Option 1: Simple but expensive.

struct winrt::param::variant
{
    template<typename T>
    variant(T&& arg) : value(box_value(arg)) {}

    IInspectable value;
};

consume_Blah(winrt::param::variant arg);

This adds an AddRef/Release to the cost of an IInspectable parameter.

Option 2:

struct winrt::param::variant
{
    template<typename = std::enable_if_t<std::is_base_of_v<Windows::Foundation::IInspectable, std::decay_t<T>>>
    variant(T&& arg) : value(get_abi(arg)) {}

    template<typename U>
    variant(U&& arg) : temp(box_value(arg)) { value = get_abi(temp); }

    void* value;
    IInspectable temp;
};

consume_Blah(winrt::param::variant arg);

This avoids the AddRef/Release for inbound IInspectables, but still costs a null check when the variant destructs (assuming the compiler can't optimize it out).

Option 3: Overload the consumer to accept either IInspectable or param::boxed_value

struct winrt::param::boxed_value
{
    template<typename U>
    variant(U&& arg) : value(box_value(arg)) { }

    IInspectable value;
};

consume_Blah(IInspectable arg);
consume_Blah(winrt::param::boxed_arg arg);

This avoids the overhead in the case where the caller passes an IInspectable, but it results in 2^N overload explosion.

Option 4: Use CTAD

template<bool boxed>
struct variant
{
    template<typename U>
    variant(U&& arg) : value(box_value(arg)) { }
    IInspectable value;
};

template<>
struct variant<false>
{
    variant(Insp const& arg) : value(&arg) { }
    const void* value;
};

template<typename T, typename = std::enable_if_t<!std::is_base_of_v<IInspectable, std::decay_t<T>>, int>>
variant(T&&) -> variant<true>;
template<typename T, typename = std::enable_if_t<std::is_base_of_v<IInspectable, std::decay_t<T>>, void>>
variant(T&&) -> variant<false>;

template<bool boxed1>
consume_Blah(winrt::param::varaint<boxed1> arg);

This still has 2^N explosion (since each variant parameter gets a different bool parameter for its variant) but you only emit the code once.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions