Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto convert const char[] to const char * in template function

Tags:

c++

templates

I'm having problems with templates whereby if you try and give a templated function a string argument, the compiler interprets "Hello World" as const char [12]. I would like it to be const char *.

I can 'work around' the problem by static casting each string to 'const char*', but since I'm trying to use this as part of a logging system, making it simple is a big goal.

Since its difficult to explain what i mean, i have come up with a simple reproducer. You will see that the last line of the main function doesn't compile.

Any help would be greatly appreciated

#include <string>

// Trivial base class so we can use polymorphism
class StoreItemsBase
{
public:
    StoreItemsBase() {}
};

// Example of a trivial Templated class to hold some 3 items.
// Intent to have similar classes to hold 4,5..n items
template <typename T1, typename T2, typename T3>
class Store3Items : public StoreItemsBase
{
public:
    Store3Items(const T1& t1, const T2& t2, const T3& t3)
    :
    StoreItemsBase(),
    mT1(t1),
    mT2(t2),
    mT3(t3)
    {}

private:
    T1 mT1;
    T2 mT2;
    T3 mT3;
};

// Function to create a pointer to our object with added id
// There would be similar CreateHolderFunctions for 4,5..n items
template <typename T1, typename T2, typename T3>
StoreItemsBase* CreateHolder(const T1& t1, const T2& t2, const T3& t3)
{
    return new Store3Items<T1, T2, T3>(t1, t2, t3);
}

int main()
{
    int testInt=3;
    double testDouble=23.4;
    const std::string testStr("Hello World");

    StoreItemsBase* Ok1 = CreateHolder(testInt, testDouble, testStr);
    StoreItemsBase* Ok2 = CreateHolder(testDouble, testStr, testInt);
    StoreItemsBase* Ok3 = CreateHolder(testStr, static_cast<const char*>("Hello there"), testInt);
    // If you try a standard string, it compiler complains
    // Although I could surround all my strings with the static cast, what I am looking for is a way
    // to for the CreateHolder function to do the work for me
    StoreItemsBase* NotOk4 = CreateHolder(testStr, "Hello World", testInt);

    // Free our objects not shown in the example
}

Compiler error is:

example.cpp: In constructor ‘Store3Items::Store3Items(const T1&, const T2&, const T3&) [with T1 = std::basic_string, T2 = char [12], T3 = int]’:
example.cpp:50:50:   instantiated from ‘StoreItemsBase* CreateHolder(const T1&, const T2&, const T3&) [with T1 = std::basic_string, T2 = char [12], T3 = int]’
example.cpp:65:74:   instantiated from here
example.cpp:21:11: error: array used as initializer
like image 481
Drew Avatar asked May 17 '12 23:05

Drew


2 Answers

You could use a metafunction to transform the types passed as argument to your templates. Any array of chars would be transformed into a char*:

template< typename T > struct transform
{
    typedef T type;
};

template< std::size_t N > struct transform< char[N] >
{
    typedef char* type;
};
template< std::size_t N > struct transform< const char[N] >
{
    typedef const char* type;
};

Then, instead of using Tn directly you would use typename transform< Tn >::type.

Update: If you are working in C++11, then std::decay already does what you want.

like image 129
K-ballo Avatar answered Nov 17 '22 11:11

K-ballo


Try changing the template arguments to const T1 t1, const T2 t2, const T3 t3. It will be less performant but it compiles

Deciding on function arguments based on template arguments is often difficult. You can try this for the class constructor signature. I used template specialization of the class "arg_type" (non-standard) to make sure all arguments types that are not const pointers are passed by const ref and all const pointers are passed as const pointers.

Also, don't forget the virtual destructor on your base class or bad things might happen :)

#include <string>

// Trivial base class so we can use polymorphism
class StoreItemsBase
{
public:
    StoreItemsBase() {}
    virtual ~StoreItemsBase() {}
};

template <typename TYPE> class arg_type
{
public:
    typedef const TYPE& type;
};
template <typename TYPE> class arg_type<const TYPE*>
{
public:
    typedef const TYPE* type;
};

// Example of a trivial Templated class to hold some 3 items.
// Intent to have similar classes to hold 4,5..n items
template <typename T1, typename T2, typename T3>
class Store3Items : public StoreItemsBase
{
    typedef typename arg_type<T1>::type arg1;
    typedef typename arg_type<T2>::type arg2;
    typedef typename arg_type<T3>::type arg3;
public:
    Store3Items(arg1 t1, arg2 t2, arg3 t3)
    :
    StoreItemsBase(),
    mT1(t1),
    mT2(t2),
    mT3(t3)
    {}

private:
    T1 mT1;
    T2 mT2;
    T3 mT3;
};

// Function to create a pointer to our object with added id
// There would be similar CreateHolderFunctions for 4,5..n items
template <typename T1, typename T2, typename T3>
StoreItemsBase* CreateHolder(const T1 t1, const T2 t2, const T3 t3)
{
    return new Store3Items<T1, T2, T3>(t1, t2, t3);
}

int main()
{
    int testInt=3;
    double testDouble=23.4;
    const std::string testStr("Hello World");

    StoreItemsBase* Ok1 = CreateHolder(testInt, testDouble, testStr);
    StoreItemsBase* Ok2 = CreateHolder(testDouble, testStr, testInt);
    StoreItemsBase* Ok3 = CreateHolder(testStr, static_cast<const char*>("Hello there"), testInt);
    // If you try a standard string, it compiler complains
    // Although I could surround all my strings with the static cast, what I am looking for is a way
    // to for the CreateHolder function to do the work for me
    StoreItemsBase* NotOk4 = CreateHolder(testStr, "Hello World", testInt);

    // Free our objects not shown in the example
}

Keep in mind, your class will be storing a raw const char* internally (not storing a std::string) so make sure the scope of that string being passed in will be longer lived than the pointer you're storing. Constant strings like in your example are fine because they live forever.

like image 38
cppguy Avatar answered Nov 17 '22 10:11

cppguy