Argument Replaced With Default In Function Call When Switching From Emscripten 4.0.1 To 4.0.2

by ADMIN 94 views

Introduction

When switching from Emscripten 4.0.1 to 4.0.2, a simple C++ program no longer works as expected when using C++23. However, it works as expected when using C++20 and C++17. This issue arises from the default argument in the function "compute_area" signature being replaced with its default value instead of being replaced in the main function. This problem also occurs when changing the default value.

Problem Description

The following program demonstrates the issue:

// main.cpp
#include "A.hpp"
#include <cassert>
#include <iostream>

auto main() -> int {
    MyStruct in_val{.l = 2.0, .exist = true};
    std::cout << "main : in_val.l : " << in_val.l << "\n";
    std::cout << "main : in_val.exist : " << in_val.exist << " \n\n";
    double val = compute_area(1.0, in_val);
    std::cout << "result = " << val << std::endl;
    assert(val == 2.0);
    return 0;
}
// A.hpp
#pragma once
#include <iostream>
struct MyStruct {
    double l{-9999.0};
    bool exist{false};
};

constexpr MyStruct default_val;

auto compute_area(double l, const MyStruct &val = default_val) -> double {
    std::cout << "compute_area : val.l : " << val.l << "\n";
    std::cout << "compute_area : val.exist : " << val.exist << " \n";
    if (val.exist) {
        return val.l * l;
    }
    return -9999;
}

Version of Emscripten/EMSDK

emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.2 (7591f1c5ea0adf6f4293cfba2995ee9700aa0d93)
clang version 21.0.0git (https:/github.com/llvm/llvm-project 9534d27e3321a3b9e6e79fe6328445575bf26b7b)
Target: wasm32-unknown-emscripten
Thread model: posix

Full Link Command and Output with -v Appended

emcc main.cpp -std=c++23 -v -o main.html
 /home/User0/emsdk/upstream/bin/clang -target wasm32-unknown-emscripten -fignore-exceptions -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=/home/User0/emsdk/upstream/emscripten/cache/sysroot -DEMSCRIPTEN -Xclang -iwithsysroot/include/fakesdl -Xclang -iwithsysroot/include/compat -std=c++23 -v -c main.cpp -o /tmp/emscripten_temp_srqc64ku/main_0.o
clang version 21.0.0git (https:/github.com/llvm/llvm-project 9534d27e3321a3b9e6e79fe6328445575bf26b7b)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/User0/emsdk/upstream/bin
 (in-process)
 "/home/User0/emsdk/upstream/bin/clang-21" -cc1 -triple wasm32-unknown-emscripten -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name main.cpp -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-cpu generic -fvisibility=hidden -debugger-tuning=gdb -fdebug-compilation-dir=/home/User0/embug -v -fcoverage-compilation-dir=/home/User0/embug -resource-dir /home/User0/emsdk/upstream/lib/clang/21 -D EMSCRIPTEN -isysroot /home/User0/emsdk/upstream/emscripten/cache/sysroot -internal-isystem /home/User0/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1 -internal-isystem /home/User0/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1 -internal-isystem /home/User0/emsdk/upstream/lib/clang/21/include -internal-isystem /home/User0/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten -internal-isystem /home/User0/emsdk/upstream/emscripten/cache/sysroot/include -std=c++23 -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fcxx-exceptions -fignore-exceptions -fexceptions -iwithsysroot/include/fakesdl -iwithsysroot/include/compat -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o /tmp/emscripten_temp_srqc64ku/main_0.o -x c++ main.cpp
clang -cc1 version 21.0.0git based upon LLVM 21.0.0git default target x86_64-unknown-linux-gnu
ignoring nonexistent directory "/home/User0/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "/home/User0/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
 /home/User0/emsdk/upstream/emscripten/cache/sysroot/include/fakesdl
 /home/User0/emsdk/upstream/emscripten/cache/sysroot/include/compat
 /home/User0/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1
 /home/User0/emsdk/upstream/lib/clang/21/include
 /home/User0/emsdk/upstream/emscripten/cache/sysroot/include
End of search list.
 /home/User0/emsdk/upstream/bin/clang --version
 /home/User0/emsdk/upstream/bin/wasm-ld -o main.wasm /tmp/emscripten_temp_srqc64ku/main_0.o -L/home/User0/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten -L/home/User0/emsdk/upstream/emscripten/src/lib -lGL-getprocaddr -lal -lhtml5 -lstubs-debug -lnoexit -lc-debug -ldlmalloc-debug -lcompiler_rt -lc++-noexcept -lc++abi-debug-noexcept -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr /tmp/tmp2enyv1ahlibemscripten_js_symbols.so --strip-debug --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_get_current --export=emscripten_stack_init --export=_emscripten_stack_alloc --export=__wasm_call_ctors --export=_emscripten_stack_restore --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-if-defined=main --export-if-defined=__main_argc_argv --export-if-defined=fflush --export-table -z stack-size=65536 --no-growable-memory --initial-heap=16777216 --no-entry --stack-first --table-base=1
 /home/User0/emsdk/upstream/bin/llvm-objcopy main.wasm main.wasm --remove-section=.debug* --remove-section=producers --remove-section=name
 /home/User0/emsdk/node/20.18.0_64bit/bin/node /home/User0/emsdk/upstream/emscripten/src/compiler.mjs /tmp/tmpn5435n35.json
 /home/User0/emsdk/node/20.18.0_64bit/bin/node /home/User0/emsdk/upstream/emscripten/tools/preprocessor.mjs /tmp/emscripten_temp_srqc64ku/settings.js shell.html

Analysis and Solution

The issue arises from the default argument in the function "compute_area" signature being replaced with its default value instead of being replaced in the main function. This problem also occurs when changing the default value.

To solve this issue, we need to ensure that the default argument is replaced with its default value in the main function. We can do this by using the std::optional class to represent the default argument.

Here is the modified code:

// A.hpp
#pragma once
#include <iostream>
#include <optional>

struct MyStruct {
    double l{-9999.0};
    bool exist{false};
};

constexpr MyStruct default_val;

auto compute_area(double<br/>
**Argument Replaced with Default in Function Call When Switching from Emscripten 4.0.1 to 4.0.2: Q&A**
=====================================================================================

**Q: What is the issue with the default argument in the function "compute_area" signature?**
-----------------------------------------------------------------------------------------

A: The default argument in the function "compute_area" signature is being replaced with its default value instead of being replaced in the main function. This issue arises from the change in Emscripten version from 4.0.1 to 4.0.2.

**Q: Why does this issue occur only with C++23 and not with C++20 and C++17?**
--------------------------------------------------------------------------------

A: This issue occurs only with C++23 because of the changes in the language standard. In C++23, the default argument is evaluated at the point of function declaration, whereas in C++20 and C++17, it is evaluated at the point of function call.

**Q: How can I solve this issue?**
-----------------------------------

A: To solve this issue, you can use the `std::optional` class to represent the default argument. This will ensure that the default argument is replaced with its default value in the main function.

**Q: What is the modified code to solve this issue?**
---------------------------------------------------

A: Here is the modified code:

```cpp
// A.hpp
#pragma once
#include <iostream>
#include <optional>

struct MyStruct {
    double l{-9999.0};
    bool exist{false};
};

constexpr MyStruct default_val;

auto compute_area(double l, const std::optional<MyStruct> &val = std::nullopt) -> double {
    if (val.has_value()) {
        std::cout << "compute_area : val.l : " << val.value().l << "\n";
        std::cout << "compute_area : val.exist : " << val.value().exist << " \n";
        if (val.value().exist) {
            return val.value().l * l;
        }
    }
    return -9999;
}

Q: Why is the std::optional class used to represent the default argument?

A: The std::optional class is used to represent the default argument because it allows us to represent the absence of a value. In this case, we use std::nullopt to represent the absence of a value, and val.has_value() to check if a value is present.

Q: What are the benefits of using the std::optional class to represent the default argument?

A: The benefits of using the std::optional class to represent the default argument are:

  • It ensures that the default argument is replaced with its default value in the main function.
  • It allows us to represent the absence of a value, which is useful in this case.
  • It provides a clear and concise way to represent the default argument.

Q: Are there any other solutions to this issue?

A: Yes, there are other solutions to this issue. One solution is to use a default value for the MyStruct struct, like this:

constexpr MyStruct default_val{.l = -9999.0, .exist = false};

This will ensure that default argument is replaced with its default value in the main function. However, this solution may not be as clear and concise as using the std::optional class.