c++iccintel-oneapi

Expression template enumeration yields to "invalid arithmetic between different enumeration types" intel C++ 2024 compiler error


Compilation of (a solution containing) this code in a AAD3.h header :

#include <memory>
#include <string>

template <class E>
class Expression
{};

/************************************************************************************************************************************************************/
// CRTP (Curiously Recurring Template Pattern)
template <class LHS, class RHS>
class ExprTimes : public Expression<ExprTimes<LHS, RHS>>
{
                LHS lhs;
                RHS rhs;

public:

                // Constructor
                explicit ExprTimes
                (const Expression<LHS>& l, const Expression<RHS>& r)
                    : lhs(static_cast<const LHS&>(l)),
                    rhs(static_cast<const RHS&>(r)) {}

                double value() const
                {
                    return lhs.value() * rhs.value();
                }

                enum { numNumbers = LHS::numNumbers + RHS::numNumbers }; // The line yielding ultimately the error

                std::string writeProgram(
                    // On input, the number of nodes processed so far
                    // On return the total number of nodes processed on exit
                    size_t& processed)
                {
                    // Process the left sub-DAG
                    const std::string ls = lhs.writeProgram(processed);
                    const size_t ln = 
                        - 1;

                    // Process the right sub-DAG
                    const std::string rs = rhs.writeProgram(processed);
                    const size_t rn = processed - 1;

                    // Process this node
                    const std::string thisString = ls + rs +
                        "y" + std::to_string(processed)
                        + " = y" + std::to_string(ln) + " * y" + std::to_string(rn) + "\n";

                    ++processed;

                    return thisString;
                }

                // Input: accumulated adjoint for this node or 1 if top node
                void pushAdjoint(const double adjoint)
                {
                    lhs.pushAdjoint(adjoint * rhs.value());
                    rhs.pushAdjoint(adjoint * lhs.value());
                }
};

// Operator overload for expressions
template <class LHS, class RHS>
inline ExprTimes<LHS, RHS> operator*(
                const Expression<LHS>& lhs, const Expression<RHS>& rhs)
{
                return ExprTimes<LHS, RHS>(lhs, rhs);
}

/************************************************************************************************************************************************************/
template <class ARG>
class ExprLog : public Expression<ExprLog<ARG>>
{
                ARG arg;

public:

                // Constructor
                explicit ExprLog(const Expression<ARG>& a)
                    : arg(static_cast<const ARG&>(a)) {}

                double value() const
                {
                    return log(arg.value());
                }

                enum { numNumbers = ARG::numNumbers };

                std::string writeProgram(
                    // On input, the number of nodes processed so far
                    // On return the total number of nodes processed on exit
                    size_t& processed)
                {
                    // Process the arg sub-DAG
                    const std::string s = arg.writeProgram(processed);
                    const size_t n = processed - 1;

                    // Process this node
                    const std::string thisString = s +
                        "y" + std::to_string(processed)
                        + " = log(y" + std::to_string(n) + ")\n";

                    ++processed;

                    return thisString;
                }

                // Input: accumulated adjoint for this node or 1 if top node
                void pushAdjoint(const double adjoint)
                {
                    arg.pushAdjoint(adjoint / arg.value());
                }
};

// Operator overload for expressions
template <class ARG>
inline ExprLog<ARG> log(const Expression<ARG>& arg)
{
                return ExprLog<ARG>(arg);
}

/************************************************************************************************************************************************************/
// Number type, also an expression
class Number : public Expression<Number>
{
                double val;
                std::shared_ptr<double> adj; // Use a shared pointer so that copies of Number hold the same adjoint value (required to make lines 199 and 200 work)

public:

                // Constructor
                explicit Number(const double v) : val(v), adj(std::make_shared<double>(0.0)) {} // code modified from text to work with shared pointer

                double value() const
                {
                    return val;
                }

                double adjoint() const
                {
                    return *adj;    // code modified from text to work with shared pointer
                }

                enum { numNumbers = 1 };

                std::string writeProgram(
                    // On input, the number of nodes processed so far
                    // On return the total number of nodes processed on exit
                    size_t& processed)
                {
                    const std::string thisString = "y" + std::to_string(processed) + " = " + std::to_string(val) + "\n";

                    ++processed;

                    return thisString;
                }

                void pushAdjoint(const double adjoint)
                {
                    *adj = adjoint;     // code modified from text to work with shared pointer
                }
};

/************************************************************************************************************************************************************/
auto calculate(Number t1, Number t2)
{
    return t1 * log(t2); // the line triggering the error
}

/************************************************************************************************************************************************************/
template <class E>
constexpr auto countNumbersIn(const Expression<E>&)
{
    return E::numNumbers;
}

and the following code in a main.cpp source :

#include "AAD3.h"

int main()
{


    Number x1(2.0), x2(3.0);
    auto e = calculate(x1, x2);
    double e_val = e.value();
    int cnt = countNumbersIn(e);
    e.pushAdjoint(1.0);
    double x1_adj = x1.adjoint();
    double x2_adj = x2.adjoint();
}

yields to no error with platform toolset equal to Visual Studio 2022 (v143), even with C++ Language Standard equal to Preview - Features from the Latest C++ Working Draft (/std:c++latest). But if I set platform toolset to Intel C++ Compiler 2025 (or Intel C++ Compiler 2024) with C++ Language Standard equal to Preview - Features from the Latest C++ Working Draft (/std:c++latest) the compilation triggers the following error :

Build started at 09:56...
1>------ Build started: Project: ConsoleApplication1, Configuration: Debug x64 ------
1>In file included from C:\CODING\OTHERS\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp:1:
1>C:\CODING\OTHERS\ConsoleApplication1\ConsoleApplication1\AAD3.h(34,38): : error : invalid arithmetic between different enumeration types ('Number::(unnamed enum at ./AAD3.h:151:2)' and 'ExprLog<Number>::(unnamed enum at ./AAD3.h:94:2)')
1>   34 |         enum { numNumbers = LHS::numNumbers + RHS::numNumbers };
1>      |                             ~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~

It compiles fine with those Intel compilers only when C++ Language Standard is equal to ISO C++20 Standard (/std:c++20) or lower.

I tried naively to cast both elements of the last line to int with no success. I don't see how to correct the code to get rid of this error when I use the intel compiler.

What explains that the use of /std:c++latest triggers this error ? (Only with Intel compiler and not with the Microsoft's ones.)

(Also, I added the icc tag in case it is useful even if I think that it doesn't apply to recent or semi-recent intel compile like the Intel C++ 2024/2025 compilers (from Intel One APÏ 2024.2 and 2025.1) that I am using.)


Solution

  • This is C++ 2026's P2864R2 "Remove deprecated arithmetic conversion on enumerations" implemented in Intel C++ 2024 and 2025 compilers but not yet in Microsoft's v143 with std:latest. You'd have the same with clang's current trunk version wtih `-std=c++26`, while g++ current trunk version would only issue a warning.

    To correct your code regarding this, you could replace the numNumbers enumeration with static const int numNumbers, for instance.