LINUX.ORG.RU

Запретить или разрешить вызов метода класса в зависимости от шаблонного типа

 ,


0

4

Недавно была одна задача и в голове родился вариант ее реализации. Но для этого нужна сия сущность. Думал как это осуществить, но так и толкового ничего не пришло. Суть вот в чем - допустим, у нас есть некий шаблонный класс Foo с функцией bar:

template<class _T>
class Foo
{
public:
    void bar();
};

И теперь нужно сделать так, чтобы при инстанцировании и создании обьекта с целочисленным типом эту функцию можно было вызвать и она работала, а при попытке создать обьект с другим типом параметра и в дальнейшем вызвать функцию bar выкидывался static assertion или что-то подобное. При этом, если создать объект не с целочисленным параметром и не вызывать эту функцию - все работало. Специализация тут не подходит, ибо должно работать для std::is_integral == true, для остального нет. И при этом, чтобы просчитывалось при компиляции и в бинарник лишнего не попадало.

P.S Да, запросы огромные. Но если родили каким-то чудом Boost, то уж это, думаю точно можно.



Последнее исправление: crarkie (всего исправлений: 1)

Мне думается, что у вас взаимно противоречивые требования:

При этом, если создать объект не с целочисленным параметром и не вызывать эту функцию - все работало. Специализация тут не подходит, ибо должно работать для std::is_integral == true, для остального нет. И при этом, чтобы просчитывалось при компиляции и в бинарник лишнего не попадало.

Так чего же вы хотите: чтобы при std::is_integral==true метод Foo::bar был в наличии и его можно было вызвать, а при std::is_integral==false чтобы что? Чтобы его не было вообще?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

crarkie
() автор топика
Ответ на: комментарий от DarkEld3r

Не. Тогда с неинтегральными типами вообще не скомпилируеться, ибо enable_if<is_integral<T>>::type не определен будет. Этот вариант мне самый первый на ум пришел. Но SFINAE тут не реализуешь. Возможно надо применять стратегию и наследоваться как то от нее. Реализовать шаблонную стратегию. Одна специализация будет иметь эту функцию, а другая нет. Хмм.. Надо попробовать..

crarkie
() автор топика
Ответ на: комментарий от crarkie

Ну так выше уже предложили одно решение.

Еще одно, более сложное, может иметь такой вид:

template<bool is_integral, typename T>
class Foo_impl {
public:
  void foo() {...}
  void bar() {...}
  void baz() {...}
};
template<typename T>
class Foo_impl<false, T> {
public:
  void foo() {...} 
  void baz() {...}
};

template<typename T>
class Foo : public Foo_impl<std::is_integral<T>::value, T> {
};
В этом случае у вас в Foo могут быть вообще разные наборы методов (или методы разных форматов) в зависимости от T.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

В принципе, должно быть можно и так написать:

template<typename T>
class Foo_impl<false, T> {
public:
  void foo() {...}
  void bar() = delete; 
  void baz() {...}
};

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Да, уже сделал примерно так, после и увидел. Вот вышло:

template<bool> struct BarFunc
{};

template<> struct BarFunc<true>
{
    void bar() {};
};

template<class _T>
class Foo : public BarFunc<std::is_integral<_T>::value>
{
};
crarkie
() автор топика
Ответ на: комментарий от eao197

А вариант выше с enable_if, как уже писал, не работает даже если не вызывать, ибо type не определен будет при non integral

crarkie
() автор топика

Мое предложение: (Я не видел предыдущих аналогичных ответов, но смысл один)

#include <functional>

class PublicFooBar {
public:
    void bar() {
    }
};

class PrivateFooBar { // or protected if you want to use this method inside the class...
private:
    void bar() {
    }
};

template <typename T>
class Foo: public std::conditional<std::is_integral<T>::value, PublicFooBar, PrivateFooBar>::type {
    using Parent = typename std::conditional<std::is_integral<T>::value, PublicFooBar, PrivateFooBar>::type;
public:
    ~Foo() { }

    using Parent::bar;
};

int main() {
    Foo<int> foo1;

#ifdef FAIL
    Foo<char*> foo2;
#endif

    return 0;
}
$ g++ main.cpp 
$ g++ -DFAIL main.cpp 
main.cpp: In instantiation of ‘class Foo<char*>’:
main.cpp:28:13:   required from here
main.cpp:16:7: error: ‘void PrivateFooBar::bar()’ is private within this context
 class Foo: public std::conditional<std::is_integral<T>::value, PublicFooBar, PrivateFooBar>::type {
       ^~~
main.cpp:11:7: note: declared private here
  void bar() {
       ^~~
rymis ★★
()
Последнее исправление: rymis (всего исправлений: 1)
Ответ на: комментарий от eao197

Теперь бы придумать, как незаметно передать в эту стратегию указатель this на текущий обьект и позволить работать со скрытыми полями ей. Это будет посложнее..

crarkie
() автор топика
Ответ на: комментарий от crarkie

Реализовать можно, только вот выглядит оно, конечно...

template<class T>
class Foo
{
public:
    template <typename P = int,
              typename std::enable_if<std::is_integral<T>::value && std::is_integral<P>::value, int>::type = 0>
    void bar() { /* ... */ }

    template <typename P = int,
              typename std::enable_if<!std::is_integral<T>::value && std::is_integral<P>::value, int>::type = 0>
    void bar() = delete;
};

xaizek ★★★★★
()
Ответ на: комментарий от xaizek

Хороший вариант, почему-то про него сразу не подумал. Итоговый вариант такой вышел у меня.

template<class _T>
class Foo;

template<bool, class _T> struct BarFunc
{

};

template<class _T> struct BarFunc<true, _T>
{
    void bar() {
        Foo<_T> *foo = static_cast<Foo<_T> *>(this);
        std::cout << foo->val_;
    };
};

template<class _T>
class Foo : public BarFunc<std::is_integral<_T>::value, _T>
{
public:
    Foo() : val_(10) {}
    friend struct BarFunc<std::is_integral<_T>::value, _T>;
private:
    _T val_;
};
Но не нравится этот костыль с кастом к Foo<_T> *. Поэтому, может все-таки твой вариант попробовать. Из итогового кода он же выкинет первую функцию, если T = float, к примеру? И не совсем пойму, для чего там std::is_integral<P>::value?

crarkie
() автор топика
Ответ на: комментарий от crarkie

Из итогового кода он же выкинет первую функцию, если T = float, к примеру?

Да, а при попытке вызова из-за второй строки компилятор скажет, что это функция удалена, ошибка довольно краткая.

И не совсем пойму, для чего там std::is_integral<P>::value?

Чтобы сделать dependent-контекст и отложить анализ функции до момента вызова.

xaizek ★★★★★
()
template<class _T>
class Foo
{
public:
    template <typename = enable_if/* lah-lah-lah*/ >
    void bar();
};
anonymous
()
#include <type_traits>

template<typename T>
class Foo{
	public:
                void simple(){}
		void bar(){
			static_assert(std::is_integral<T>::value,
					"T must be integral");
		}
};

int main(){
	Foo<int> a;
	Foo<double> b;
	a.bar();
//	b.bar();
        a.simple();
        b.simple();
}
userd
()

Напиши пример, как бы ты хотел использовать получившийся тип.

Такое ощущение, что ты либо написал нам не то, что думаешь, либо std::enable_if таки тебе подходит.

pon4ik ★★★★★
()

Немного побурчу. А между прочим, вполне рядовая задача для других языков программирования. Для того же Rust. И код с эффективностью шаблонов получится

dave ★★★★★
()
Ответ на: комментарий от pon4ik

Лень искать код, ибо поздно уже. Нам в универе задали написать класс-аналог целочисленного статического массива, но с проверками на выход за пределы массива и поддержкой копераций деления на число всех элементов, умножения, сложения двух одинаковых массивов и т.д. Ну и еще с поддержкой произвольных индексов. Ну, а так как я убежал далеко вперед уже давно по изучению языка(группа вся еще только перегрузку операторов прошла и конструкторы/деструкторы - даже до наследования еще не дошла), то решил усложнить себе задачу. И написал такой же аналог, но с поддержкой задания стратегии CheckRanges/NoCheck при создании объекта, задания в шаблоне же произвольных индексов(вернее нижний индекс и размер массива), ну и произвольного типа элементов. И определить арифм.операции только для соответствующих типов. Тогда я сделал как-то так:

//...
template<class T>
std::enable_if_t<std::is_arithmetic<T>::value && std::is_arithmetic<value_type>::value, array> &operator*=(T val)
{
//...
}
//...

Ну и естественно это не сработало так как нужно. Потом забыл. А щас вот открыл и решил все-таки разобраться что и как.

crarkie
() автор топика

А банальное

template<class T> class Foo {
public:
  void bar() {
    static_assert(is_integral<T>::value, "NEED INTEGRAL!!1")
    ...
  }
}
не сработает? Мне лень проверять.

Miguel ★★★★★
()
Ответ на: комментарий от crarkie

Не. Тогда с неинтегральными типами вообще не скомпилируеться

Да, ты прав.

DarkEld3r ★★★★★
()
Ответ на: комментарий от dave

Немного побурчу. А между прочим, вполне рядовая задача для других языков программирования. Для того же Rust. И код с эффективностью шаблонов получится

А вы могли бы пару примеров привести? Реально интересно, как в других языках программирования решается задача наличия/отсутствия метода в классе в зависимости от типа шаблона.

eao197 ★★★★★
()
Ответ на: комментарий от dave

А между прочим, вполне рядовая задача для других языков программирования. Для того же Rust.

trait T {
    fn foo();
    fn bar();
}

И что же надо сделать, чтобы реализация bar была только для целых чисел?

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Обявить новый trait для нужного типа данных (реализовать impl для целых чисел), а потом для bar поставить условие where:

trait IntT {}
trait T {
    fn bar(&self, ...) where Self: IntT;
}

Псевдокод, но идея простая.

dave ★★★★★
()
Ответ на: комментарий от eao197

В Haskell - через type class constraints. В C#, F# и Rust - через where. В Scala и Java тоже есть похожие на where штуки.

Из перечисленных языков Rust дает при кодогенерации аналог плюсовых шаблонов. Название термина не помню, но суть в том, что generics раскрывается в отдельный код для каждого типа как и с шаблонами.

dave ★★★★★
()
Ответ на: комментарий от dave

В C#, F# и Rust - через where.

AFAIK, выражение where накладывает ограничение на параметр шаблона. Т.е. если определяется шаблон X с параметром Y, то через where можно наложить требования на Y. Но как поменять состав X в зависимости от Y?

Если можно, то с примером кода.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

А что ты здесь имеешь в виду? Слишком абстрактно

dave ★★★★★
()
Ответ на: комментарий от dave

Тут либо я не понял, либо вы. Приведенный вами код не позволит в T::bar подсунуть num, если тип num не реализует трейт IntT. Но как в вашем T сделать разный набор методов в зависимости от параметра трейта?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

я, кстати, там неправильно написал. Объект не может быть типа Y1 или Y2. Там должны быть еще параметры типов, потому что реальные типы отличаются от traits. Сейчас подумаю на твоим примером

dave ★★★★★
()
Ответ на: комментарий от eao197

Да, по-моему так съест.

И чисто для проформы ради, тот пример должен был выглядеть иначе:

trait Y1 {}
trait Y2 {}
trait X {
    type Y;
    fn bar1<Z>(&self, z: Z) where Self::Y: Y1, Z: Y1;
    fn bar2<Z>(&self, z: Z) where Self::Y: Y2, Z: Y2;
}
dave ★★★★★
()
Ответ на: комментарий от eao197

Но как поменять состав X в зависимости от Y?

В шарпах и явах так не сделать, потому что Foo<T> - это такой тип, и однажды увиденное там нельзя развидеть. Поэтому надо наследование городить и фабрику.

RedPossum ★★★★★
()
Ответ на: комментарий от lovesan

Вот это примерно то, за реализацию чего на шаблонах C++ надо отрывать руки.

Почему?

DarkEld3r ★★★★★
()
Ответ на: комментарий от dave

Да, по-моему так съест.

У меня получилось вот так: https://is.gd/ciTkQz При реализации трайта для структуры нужно таки определять все методы, которые перечислены в трейте X. Но вызывать можно только те, которые разрешены для X::Y выражением where.

Ну и да, вот это вот:

fn bar1<Z>(&self, z: Z) where Self::Y: Y1, Z: Y1;

Это, блин, явный подгон под то, что удобно вам, а не то, что требуется сделать.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Кстати, да. Может быть, в Java и .NET так нельзя. Мой мозг давно испорчен Haskell :) К счастью, Rust очень много впитал в себя не только из Си и Си++, но и из Haskell тоже

Твой код выглядит немного странно, потому что where в trait имеет обычно смысл там, где уже приводится реализация на уровне trait. Поэтому повторять ее в impl уже бывает ненужно

dave ★★★★★
()
Ответ на: комментарий от dave

Твой код выглядит немного странно

Мой код — это всего лишь попытка проверить ваши предположения. Так что хотите доказать возможность написать на Rust-е _шаблон_, содержимое которого меняется в зависимости от типа параметра шаблона, то будьте добры привести минимальный компилирующийся пример.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Из библиотеки:

 
impl<T> Cell<T> 
where
    T: Copy, 
fn get(&self) -> T
Cell - дженерик структура. Метод get присутствует только когда шаблоный параметр реализует Copy, в противном случае get нет, потому что иначе он бы убивал содержимое Cell.

khrundel ★★★★
()
Ответ на: комментарий от dave

Псевдокод, но идея простая.

В плюсах, как по мне, будет проще, учитывая, что IntT придётся для кучи типов реализовывать.

DarkEld3r ★★★★★
()
Ответ на: комментарий от dave

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

«Monomorphization».

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Вроде есть крейт, где такой интерфейс уже реализован

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Что значит «с данными»?

template<typename T>
struct S
{
    T a;
    int b;
};
 
template<>
struct S<int>
{
    float d;
};

Если шаблон с числовым параметром, то нет, таких дженериков в ржавом нет.

При помощи костыля крейта можно.

DarkEld3r ★★★★★
()
Ответ на: комментарий от xaizek

В новой теме увидел, как это же сделать короче:

template<class T>
class Foo
{
public:
    template <typename P = T,
              typename std::enable_if<std::is_integral<P>::value, int>::type = 0>
    void bar() { /* ... */ }

    template <typename P = T,
              typename std::enable_if<!std::is_integral<P>::value, int>::type = 0>
    void bar() = delete;
};

xaizek ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.