Dimitrije Kostic
Dimitrije Kostic

Reputation: 305

Compiler error from type-checking a template parameter

I'm trying to write a templated class that handles different template arguments differently. I would like the class to support two data types: unsigned ints and std::strings and nothing else. The class needs to behave a little differently depending on which type it gets, so I do some type-checking in the constructor.

Here's stripped-down code for what I'm doing.

#include <iostream> 
#include <memory>
#include <utility> 
#include <map> 
typedef unsigned int uint;

template<typename input_id_t> class atom_t
{
    public:
        atom_t(input_id_t d) : 
            data(d)
         { }
        
        const input_id_t data;
};

template<typename input_id_t> using atom_ptr = std::shared_ptr<atom_t<input_id_t>>;
template<typename input_id_t> using atom_map_t = std::map<input_id_t, atom_ptr<input_id_t>>;

template<typename input_id_t> class abstract_t
{
    public:

        abstract_t()
        {
            if (typeid(input_id_t).name() == typeid(uint).name())
            {
                this->base_entry = std::make_shared<atom_t<input_id_t>>(0);
                this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>(0, this->base_entry));
            }
            else if (typeid(input_id_t).name() == typeid(std::string).name())
            {
                this->base_entry = std::make_shared<atom_t<input_id_t>>("");
                this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>("", this->base_entry));
            }
            else { throw "abstract_t template argument invalid!\n"; }
        }

        atom_ptr<input_id_t> base_entry; 

        atom_map_t<input_id_t> * a_map = new atom_map_t<input_id_t>;
};

int main() 
{
    abstract_t<uint> A;
} 

I have a few questions.

  1. The errors this code yields are below. I think they're all related to the call to std::pair, but what's wrong? I noticed that deleting the else if clause resolves them.

    error: no matching constructor for initialization of 'std::pair<unsigned int, atom_ptr >' (aka 'pair<unsigned int, shared_ptr<atom_t > >') this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>("", this->base_entry));

    error: static_assert failed due to requirement 'is_constructible<atom_t, char const (&)[1]>::value' "Can't construct object in make_shared

    error: no matching constructor for initialization of 'atom_t'

  2. I realize the typeid(input_id_t).name() == typeid(uint/std::string).name() approach is clunky. Is there a better way to do this?

Upvotes: 1

Views: 376

Answers (2)

catnip
catnip

Reputation: 25388

The problem is that the compiler tries to compile the else branch of your if statement, even if that branch is never taken. With C++17 and later, this can be avoided by using a combination of std::is_same and if constexpr, like so:

    abstract_t()
    {
        if constexpr (std::is_same_v <input_id_t, uint>)
        {
            this->base_entry = std::make_shared<atom_t<input_id_t>>(0);
            this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>(0, this->base_entry));
        }
        else if constexpr (std::is_same_v <input_id_t, std::string>)
        {
            this->base_entry = std::make_shared<atom_t<input_id_t>>("");
            this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>("", this->base_entry));
        }
        else { throw "abstract_t template argument invalid!\n"; }
    }

And that also answers your second question. But I prefer Roland's answer, it encapsulates the two constructors better.

Live demo

Upvotes: 3

Roland W
Roland W

Reputation: 1461

The problem is that the code for your std::string case is still compiled, even though it is not executed, and you cannot construct a pair<int, atom_ptr<int>> with "" as first argument. That is what the compiler tells you.

The better approach is to use template specializations:

template<typename input_id_t> class abstract_t {};

template<> class abstract_t<uint> {
public:
    typedef uint input_id_t;
    abstract_t()
    {
        this->base_entry = std::make_shared<atom_t<input_id_t>>(0);
        this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>(0, this->base_entry));
    }

    atom_ptr<input_id_t> base_entry; 
    atom_map_t<input_id_t> * a_map = new atom_map_t<input_id_t>;
}

template<> class abstract_t<std::string> {
public:
    typedef std::string input_id_t;
    abstract_t()
    {
        this->base_entry = std::make_shared<atom_t<input_id_t>>("");
        this->a_map->insert(std::pair<input_id_t, atom_ptr<input_id_t>>("", this->base_entry));
    }

    atom_ptr<input_id_t> base_entry;
    atom_map_t<input_id_t> * a_map = new atom_map_t<input_id_t>;
}

Upvotes: 3

Related Questions