Search code examples
c++one-definition-ruleif-constexpr

Is it an ODR violation to have an inline templated function have different behavior due to if constexpr?


It's possible to detect if a type is complete https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678

I have reason to want to provide a different (inlinable) implementation if a type is complete.

Is it an ORD violation for a templated function to behave differently in different translation units based on an if constexpr in the function? In a sense, it's the "same" function in the different translation units (it's not macros creating different definitions at a C++ source level, which is an ODR violation).

Here's a simple example:

#include <iostream>
#include <type_traits>


// From https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678
template<typename, typename = void>
constexpr bool is_type_complete_v = false;

template<typename T>
constexpr bool is_type_complete_v
    <T, std::void_t<decltype(sizeof(T))>> = true;


template <typename T>
T* loggingGetRef(T* x) {
    if constexpr (is_type_complete_v<T>) {
        std::cout << "Complete!" << std::endl;
    } else {
        std::cout << "Incomplete!" << std::endl;
    }
    return x;
}

struct S
//{} // <- Uncomment this to make loggingGetRef be "different" through if constexpr.
;

int main() {    
    S* ptr = nullptr;
    loggingGetRef(ptr);    
}

https://godbolt.org/z/q1soa58PY

For my application, I would want the two if constexpr branches to outwardly act the same (unlike this example that prints different things) so from a correctness standpoint, it would be OK if the linker picked the assembly for either implementation and used that everywhere. It's just that in a translation unit where T is complete we may be able to get better performance (and I fully expect it to be inlined).


Solution

  • ODR is not based on "templated functions"; it is based on actual functions. Templates generate functions based on template parameters. Each unique set of template parameters represents a different function. Different functions generated from the same template are different functions. There are no ODR expectations between different functions, regardless of what created them.

    loggingGetRef<S> is the name of a particular function. If you do something that causes loggingGetRef<S> to generate differently due to constant expressions, you have ill-formed code (no diagnostic required). But that isn't a matter of ODR violations; that's a violation of the rules of template instantiation. A template that is instantiated with a specific set of parameters must be the same in all translation units that instantiate it with those parameters. Period.