Skip to content

Init macros for designated initializers in C++ #579

Open

Description

@Kangz

On bkaradzic/bgfx#3544 there is an example of the following code that doesn't compile in C++ but should be possible to make work if the INIT macros used designated initializers. Maybe we should consider doing this (where designated initializers are availble, so C++17?)

C++ issue, I used named parameters, but WGPU_*_INIT is using struct / unnamed...

		void initBufferBinding(WGPUBindGroupLayoutEntry& _out, uint32_t _binding, WGPUShaderStage _visibility, WGPUBufferBindingType _type, bool _hasDynamicOffset)
		{
			_out =
			{
				.nextInChain      = NULL,
				.binding          = _binding,
				.visibility       = _visibility,
				.bindingArraySize = 0,
				.buffer =
				{
					.nextInChain      = NULL,
					.type             = _type,
					.hasDynamicOffset = _hasDynamicOffset,
					.minBindingSize   = 0,
				},
				.sampler = WGPU_SAMPLER_BINDING_LAYOUT_INIT,
				.texture = WGPU_STORAGE_TEXTURE_BINDING_LAYOUT_INIT,
				.storageTexture = WGPU_STORAGE_TEXTURE_BINDING_LAYOUT_INIT,
			};
		}

Activity

  1. kainino0x commented on Jan 21, 2026

    @kainino0x
    Collaborator

    I don't understand what the issue is (unless it's the typo that uses WGPU_STORAGE_TEXTURE_BINDING_LAYOUT_INIT instead of WGPU_TEXTURE_BINDING_LAYOUT_INIT). It should be totally valid to put non-designated initializers inside designated initializers. I think after macro expansion it looks like this:

    struct Inner { int x; };
    struct Outer { Inner i; };
    
    int main() {
        Outer o = { // designated initializer
            .i = Inner{4}, // non-designated initializer
        };
        return o.i.x;
    }

    Which seems to be totally valid.

    There's further discussion on the issue hinting that the real issue might just be that WGPU_SAMPLER_BINDING_LAYOUT_INIT sets Undefined (not BindingNotUsed). The reason for this is to use it like this:

    WGPUBindGroupLayoutEntry entry = WGPU_BIND_GROUP_LAYOUT_ENTRY_INIT; // All set to BindingNotUsed
    entry.texture = WGPU_TEXTURE_BINDING_LAYOUT_INIT; // I'm using the texture field
    entry.texture.multisampled = true; // But I also need to modify it a bit

    There's no way in C to do like:

    WGPUBindGroupEntry entry = {
        ... WGPU_BIND_GROUP_LAYOUT_ENTRY_INIT, // Use the defaults
        .texture = { // But override .texture
            ... WGPU_TEXTURE_BINDING_LAYOUT_INIT, // Use the defaults
            .multisampled = true, // But override .multisampled
        },
    };

    TBH the problem is that this is a C header and using it is a pain from any language. C++ should use C++ bindings and there should be stable supported C++ bindings even if they're very basic. Then you can do things like we already do in Dawn:

    // Everything not mentioned here gets set to the default defined in the C++ struct definition
    wgpu::BindGroupLayoutEntry entry = {
        .texture = { .multisampled = true }, // This field is used so it defaults to Undefined (not BindingNotUsed)
        // Other fields default to BindingNotUsed
    };

    Technically we could put inline defaults in the header if using it from C++, but (1) that's a breaking change and (2) it's weird for app code to do something different in C than in C++.

  2. bkaradzic commented on Jan 22, 2026

    @bkaradzic

    I don't understand what the issue is (unless it's the typo that uses

    initBufferBinding3 below doesn't work because it uses named fields, and WGPU_*_INIT macros have named fields commented out.

    gcc -std=c++20 -I<webgpu.h> test.cpp

    #include <webgpu/webgpu.h>
    
    void initBufferBinding0(WGPUBindGroupLayoutEntry& _out, uint32_t _binding, WGPUShaderStage _visibility, WGPUBufferBindingType _type, bool _hasDynamicOffset)
    {
    	_out = WGPU_BIND_GROUP_LAYOUT_ENTRY_INIT;
    }
    
    void initBufferBinding1(WGPUBindGroupLayoutEntry& _out, uint32_t _binding, WGPUShaderStage _visibility, WGPUBufferBindingType _type, bool _hasDynamicOffset)
    {
    	_out =
    	{
    		.nextInChain      = NULL,
    		.binding          = _binding,
    		.visibility       = _visibility,
    		.bindingArraySize = 0,
    		.buffer =
    		{
    			.nextInChain      = NULL,
    			.type             = _type,
    			.hasDynamicOffset = _hasDynamicOffset,
    			.minBindingSize   = 0,
    		},
    		.sampler = {},
    		.texture = {},
    		.storageTexture = {},
    	};
    }
    
    void initBufferBinding2(WGPUBindGroupLayoutEntry& _out, uint32_t _binding, WGPUShaderStage _visibility, WGPUBufferBindingType _type, bool _hasDynamicOffset)
    {
    	_out =
    	{
    		.nextInChain      = NULL,
    		.binding          = _binding,
    		.visibility       = _visibility,
    		.bindingArraySize = 0,
    		.buffer =
    		{
    			.nextInChain      = NULL,
    			.type             = _type,
    			.hasDynamicOffset = _hasDynamicOffset,
    			.minBindingSize   = 0,
    		},
    		.sampler =
    		{
    			.nextInChain = NULL,
    			.type        = WGPUSamplerBindingType_BindingNotUsed,
    		},
    		.texture =
    		{
    			.nextInChain   = NULL,
    			.sampleType    = WGPUTextureSampleType_BindingNotUsed,
    			.viewDimension = WGPUTextureViewDimension_Undefined,
    			.multisampled  = false,
    		},
    		.storageTexture =
    		{
    			.nextInChain   = NULL,
    			.access        = WGPUStorageTextureAccess_BindingNotUsed,
    			.format        = WGPUTextureFormat_Undefined,
    			.viewDimension = WGPUTextureViewDimension_Undefined,
    		},
    	};
    }
    
    void initBufferBinding3(WGPUBindGroupLayoutEntry& _out, uint32_t _binding, WGPUShaderStage _visibility, WGPUBufferBindingType _type, bool _hasDynamicOffset)
    {
    // test.cpp: In function ‘void initBufferBinding3(WGPUBindGroupLayoutEntry&, uint32_t, WGPUShaderStage, WGPUBufferBindingType, bool)’:
    // test.cpp:85:9: error: no match for ‘operator=’ (operand types are ‘WGPUBindGroupLayoutEntry’ and ‘<brace-enclosed initializer list>’)
    //    85 |         };
    //       |         ^
    // In file included from test.cpp:2:
    // webgpu/webgpu.h:3273:16: note: candidate: ‘constexpr WGPUBindGroupLayoutEntry& WGPUBindGroupLayoutEntry::operator=(const WGPUBindGroupLayoutEntry&)’
    //  3273 | typedef struct WGPUBindGroupLayoutEntry {
    //       |                ^~~~~~~~~~~~~~~~~~~~~~~~
    // webgpu/webgpu.h:3273:16: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘const WGPUBindGroupLayoutEntry&’
    // webgpu/webgpu.h:3273:16: note: candidate: ‘constexpr WGPUBindGroupLayoutEntry& WGPUBindGroupLayoutEntry::operator=(WGPUBindGroupLayoutEntry&&)’
    // webgpu/webgpu.h:3273:16: note:   no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘WGPUBindGroupLayoutEntry&&’
    
    	_out =
    	{
    		.nextInChain      = NULL,
    		.binding          = _binding,
    		.visibility       = _visibility,
    		.bindingArraySize = 0,
    		.buffer =
    		{
    			.nextInChain      = NULL,
    			.type             = _type,
    			.hasDynamicOffset = _hasDynamicOffset,
    			.minBindingSize   = 0,
    		},
    		.sampler = WGPU_SAMPLER_BINDING_LAYOUT_INIT,
    		.texture = WGPU_STORAGE_TEXTURE_BINDING_LAYOUT_INIT,
    		.storageTexture = WGPU_STORAGE_TEXTURE_BINDING_LAYOUT_INIT,
    	};
    }
    
    int main()
    {
    	WGPUBindGroupLayoutEntry bgle;
    	initBufferBinding0(bgle, 0, WGPUShaderStage_Vertex, WGPUBufferBindingType_Uniform, false);
    	initBufferBinding1(bgle, 0, WGPUShaderStage_Vertex, WGPUBufferBindingType_Uniform, false);
    	initBufferBinding2(bgle, 0, WGPUShaderStage_Vertex, WGPUBufferBindingType_Uniform, false);
    	initBufferBinding3(bgle, 0, WGPUShaderStage_Vertex, WGPUBufferBindingType_Uniform, false);
    }

    TBH the problem is that this is a C header and using it is a pain from any language. C++ should use C++ bindings and there should be stable supported C++ bindings even if they're very basic. Then you can do things like we already do in Dawn:

    No. Even thought I'm using C++, I prefer to use C headers, because C++ headers introduce stylistic choices that are not in alignment with my stylistic choices. At least C is neutral.

  3. bkaradzic commented on Jan 22, 2026

    @bkaradzic

    For example this zero initialization is not consistent, and can't be used for all structs.

    		.sampler = {},
    		.texture = {},
    		.storageTexture = {},
    

    If any of these structs contained WGPUStringView label it would fail validation later. WGPU_STRING_VIEW_INIT is { NULL, SIZE_MAX } and validation treats it as not used. Where { NULL, 0 } is considered used but empty string.

  4. self-assigned this
    on Jan 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions