Принудительное использование класса шаблона с оператором T* при передаче в качестве T* аргумента шаблона функции

#c #templates #coercion

Вопрос:

Предположим, у меня есть шаблон функции, подобный этому:

 template<class T>
inline
void
doStuff(T* arr)
{
  // stuff that needs to use sizeof(T)
}
 

Затем в другом .h файле у меня есть класс шаблона Foo , который имеет:

 public: operator T*() const;
 

Теперь я понимаю, что это разные вещи. Но если у меня есть переменная Foo<Bar> f в стеке, единственный способ принудить ее к какому-либо указателю-это вызвать operator T*() . Тем не менее, если позвонить doStuff(f) , GCC жалуется, что doStuff не может Foo<Bar> вместо автоматического использования оператора T*() принудить Bar* , а затем специализировать шаблон функции с Bar помощью as T .

Могу ли я что-нибудь сделать, чтобы это работало с двумя шаблонами? Или либо аргумент функции шаблона должен быть реальным типом указателя, либо класс шаблона с оператором принуждения должен быть передан функции, не являющейся шаблоном?

Ответ №1:

GCC прав. В аргументах шаблона учитываются только точные совпадения, преобразования типов-нет. Это связано с тем, что в противном случае пришлось бы учитывать бесконечное (или, по крайней мере, экспоненциальное) количество преобразований.

Если Foo<T> является единственным другим шаблоном, в котором вы собираетесь работать, лучшим решением было бы добавить:

 template<typename T> inline void doStuff(const Foo<T>amp; arr) {
    doStuff(static_cast<T*>(arr));
}
 

Если у вас возникла эта проблема с большим количеством шаблонов, этот должен ее исправить:

 #include <boost/type_traits/is_convertible.hpp>
#include <boost/utility/enable_if.hpp>
template<template <typename> class T, typename U> inline typename boost::enable_if<typename boost::is_convertible<T<U>, U*>::type>::type doStuff(const T<U>amp; arr) {
    doStuff(static_cast<U*>(arr));
}
 

Хотя это немного многословно 😉

Ответ №2:

Возможно, это стоит попробовать:

 doStuff<Bar>(f);
 

Я думаю, что это заставит компилятор ожидать, что T* будет Bar*, а затем использовать оператор Foo T*() для выполнения приведения, но я не могу сказать, что пробовал это.

Комментарии:

1. Это правильно. Компилятор жалуется, потому что вывод аргументов шаблона не удается. Как только вы приведете аргумент, в нем не будет ничего особенного. На этом этапе компилятор знает как целевой, так и конечный тип последовательности преобразования.

Ответ №3:

Идея Леона, вероятно, лучшая. Но в крайнем случае вы также можете явно вызвать оператор приведения:

 doStuff(static_cast<Bar*>(f));
 

Ответ №4:

Я не уверен, почему преобразование не работает, но вы можете использовать перегрузку, чтобы устранить проблему

 
template 
inline
void 
doStuff(Tamp; arrRef)
{
  doStuff(amp;arrRef);
}
 

Комментарии:

1. Разыменование ссылки — это вообще плохой стиль. Я знаю, что хаки имеют свою ценность, но используются они крайне редко.

2. Кроме того, это просто неверно. Синтаксически, потому что вы нигде не определили T, семантически, потому что Foo<T>.операторamp;() вернет не T*, а Foo<T><T>*.

Ответ №5:

Ну, T* не является отдельным типом от T в том смысле, в каком вы думаете. Указатель является определителем типа. Я не уверен, что об этом говорится в стандарте, но я бы сказал, что, поскольку переменная уже относится к типу T, она не пытается преобразовать снова. Если вы хотите выполнить некоторые пользовательские действия для получения указателя, перегрузите операторamp;.

Комментарии:

1. Вы действительно не хотите перегружать оператораamp;(). Причины этого см. в разделе «Рекомендации Google».