Does static data member specialization in CRTP violate ODR?

Viewed 63

First of all, I know this question might seem a duplicate. But I've read many posts with a similar question and didn't find an answer.

Second of all, I haven't had any issue with my solution so far. So I'm asking rather if my solution is a correct one with no pitfalls.

Suppose I have a class template Particle which is supposed to be a base class in the CRTP and is intended to have a static variable fName which may differ from instance to instance:

Base

Particle.h

#pragma once

#include <iostream>
#include <string>

template<typename CONCRETE_PARTICLE>
class Particle
{
    private :
        static const std::string fName;

    public :
        static const std::string& GetName() { return fName; }
};

// Default name. If I'm not mistaken this is fine regarding ODR.
template<typename CONCRETE_PARTICLE>
const std::string Particle<CONCRETE_PARTICLE>::fName = "Unknown";

So other particles inherit (and instantiate) from this class. Suppose the user creates a new Particle and they should have a choice either to specify a name of their new particle or not. In the latter case the name would be "Unknown".

Unnamed particle

Tachyon.h

#pragma once

#include "Particle.h"

class Tachyon : public Particle<Tachyon>
{
    public :
        Tachyon();
        ~Tachyon() = default;
};

with nothing special in the Tachyon.cpp (I made .cpp files in order to check that ODR isn't violated).

On the other hand, what if the name is to be specified. What should they do?

Named particle

Muon.h

#pragma once

#include "Particle.h"

class Muon : public Particle<Muon>
{
    public :
        Muon();
        ~Muon() = default;

};

// Specification declaration (???)
// Tell the compiler that Particle<Muon>::fName is defined somewhere
// and not to take the default value into account (???)
template<>
const std::string Particle<Muon>::fName;

Muon.cpp

#include "Muon.h"

Muon::Muon() {}

template<>
const std::string Particle<Muon>::fName = "Muon";

Main.cpp

#include "Muon.h"
#include "Tachyon.h"

int main()
{
    std::cout << Particle<Muon>::GetName() << "\n"; // prints "Muon"
    std::cout << Particle<Tachyon>::GetName() << "\n"; // prints "Unknown"
    return 0;
}

As I said I don't get any error and the program works as intended. But I am a bit confused by the mix of CRTP + ODR + static data member declaration/definition so the question.

1 Answers

It’s not clear exactly what rule you think this might violate, but this program is well formed. In particular, explicit specializations of a templated static variable are definitions only if they have an initializer ([temp.expl.spec]/14). (Non-member variable templates instead use extern to merely declare specializations.) Nor is there any use of the primary template before the explicit specialization is declared so long as the derived class doesn’t do so.

That said, you could easily avoid using specializations here entirely: use SFINAE to detect a member fName and return"Unknown" if there’s none. Or just look up fName as CONCRETE_PARTICLE::fName to use any declared there. (Incidentally, don’t use MACRO_NAMES for template parameters.)

Related