Search code examples
c++templatestemplate-meta-programmingc++20c++-concepts

Decide which template to instantiate based on which concept is satisfied


Let's say I have 2 concepts:

struct Vector3 {float x; float y; float z;};

template<typename T> concept ImplementsMeshGetters = requires(T a, uint index)
{
    { a.Position(index) }->std::convertible_to<Vector3>;
};

template<typename T> concept HasMeshMemberArrays = requires(T a, uint index)
{
    { a.positions[index] }->std::convertible_to<Vector3>;
};

And I create a templated class that is constrained by either one:

template<typename MeshType>
requires(ImplementsMeshGetters<MeshType> || ImplementsMeshGetters<MeshType>)  
    class Mesh 
    {
           Vector3 GetPosition(uint i);
    };

I want 3 versions of the function GetPosition specialized, one for the first concept, one for the second, and one just in case the compiler somehow gets confused, that throws an error to alert the user that the class wasn't compiled properly.

I tried this:


template<ImplementsMeshGetters MeshType>
Eigen::Vector3f AnimationMesh<MeshType>::GetPosition(uint i)
{
    return mesh.Position(i);
};

template<HasMeshMemberArrays MeshType>
Eigen::Vector3f AnimationMesh<MeshType>::GetPosition(uint i)
{
    return mesh.positions[i];
}

template<> Eigen::Vector3f AnimationMesh<typename T>::GetPosition(uint i)
{
    Assert(
        false,
        "Somehow the mesh doesn't obey either of it's constraints. Your compiler "
        "is likely broken.");
    return {0, 0, 0};
}

But it doesn't compile as I am redefining the same symbol.

I could instead do this:

template<typename MeshType> Vector3 AnimationMesh<MeshType>::GetPosition(uint i)
{
    if constexpr(ImplementsMeshGetters<MeshType>)
    {  //
        return mesh.Position(i);
    }
    else if constexpr(HasMeshMemberArrays<MeshType>)
    {
        return mesh.positions[i];
    }
    else
        constexpr
        {
            Assert(
                false,
                "Somehow the mesh doesn't obey either of it's constraints. Your compiler "
                "is likely broken.");
            return {0, 0, 0};
        }
};

But that is a bit uglier than if I could instantiate the template.


Solution

  • In C++20 you can define constrained member functions:

    template <typename MeshType>
    class Mesh {
    public:
      Vector3 GetPosition(uint i) requires ImplementsMeshGetters<MeshType> {
        return mesh.Position(i);
      }
      Vector3 GetPosition(uint i) requires HasMeshMemberArrays<MeshType> {
        return mesh.positions[i];
      }
    private:
      MeshType mesh;
    };
    

    Live demo

    This will work as long as there's no overlap (like when some MeshType satisfies both constraints). So the if constexpr solution might be more robust as it clearly defines the priority in case of an overlap.

    Note: your attempt looks like an explicit member specialization, it won't work because to specialize a member function, all enclosing templates must also be fully specialized.