Выбор типа переменной класса в зависимости от параметра шаблона (C++17, if constexpr)?
Ты можешь добиться желаемого используя частную специализацию, например.
Суть такова. Нужно превратить значения типа перечисления в тип. Сделать мы это можем используя возможность указывать в качестве параметра шаблона значения перечислимых типов. 
Например:enum class AllowedTypes : uint8_t
{
	Int8	= 0,
	Uint8,
	Int16,
	Uint16,
};

template< AllowedTypes TYPE >
struct AllowedType;
А для того, чтобы конкретное значение перечисления правильно переводилось в тип, шаблон "AllowedType" нужно специализировать.spoilertemplate<>
struct AllowedType<AllowedTypes::Int8>
{
	using Type = int8_t;
};

template<>
struct AllowedType<AllowedTypes::Uint8>
{
	using Type = uint8_t;
};

template<>
struct AllowedType<AllowedTypes::Int16>
{
	using Type = int16_t;
};

template<>
struct AllowedType<AllowedTypes::Uint16>
{
	using Type = uint16_t;
};
После этого достаточно использовать "typename AllowedType::Type" в нужном тебе месте.spoilertemplate< AllowedTypes TYPE >
struct spi
{
	typename AllowedType<TYPE>::Type*	p_tx = nullptr;
	
	constexpr spi() {};
};
Вот пример работы:spoiler// Example program
#include <typeinfo>
#include <iostream>
#include <string>
#include <cstdint>

enum class AllowedTypes : uint8_t
{
	Int8	= 0,
	Uint8,
	Int16,
	Uint16,
};

template< AllowedTypes TYPE >
struct AllowedType;

template<>
struct AllowedType<AllowedTypes::Int8>
{
	using Type = int8_t;
};

template<>
struct AllowedType<AllowedTypes::Uint8>
{
	using Type = uint8_t;
};

template<>
struct AllowedType<AllowedTypes::Int16>
{
	using Type = int16_t;
};

template<>
struct AllowedType<AllowedTypes::Uint16>
{
	using Type = uint16_t;
};

template< AllowedTypes TYPE >
using TypeFor = typename AllowedType<TYPE>::Type;


template< AllowedTypes TYPE >
struct spi
{
	TypeFor<TYPE>*	p_tx = nullptr;
	
	constexpr spi() {};
};

int main()
{
	spi<AllowedTypes::Int8> spi1;
	std::cout << typeid( *spi1.p_tx ).name() << " " << sizeof( *spi1.p_tx ) << std::endl;
	
	spi<AllowedTypes::Uint16> spi2;
	std::cout << typeid( *spi2.p_tx ).name() << " " << sizeof( *spi2.p_tx ) << std::endl;
	
	return 0;
}
cpp.sh/85p6
Возможно вам нужно что-то подобное:#include <iostream>
#include <type_traits>

template <typename U>
struct Data {
    int value;
};
template <>
struct Data<double> {
    double value;
};

template <typename T>
class Test {

    Data<T> data;

public:
    void test()
    {
        if (typeid(data.value).hash_code() == typeid(int).hash_code())
            std::cout << "int";
        else if (typeid(data.value).hash_code() == typeid(double).hash_code())
            std::cout << "double";
        else
            std::cout << "other";

        std::cout << std::endl;
    }
};

int main(int argc, char* argv[])
{
    Test<int> t1;
    Test<double> t2;
    Test<char> t3;

    t1.test();
    t2.test();
    t3.test();

    return 0;
}
data.value во всех случаях int за исключением того, когда T double. Решение конечно не самое красивое и магистры шаблонной магии возможно побьют меня за него, но работает :)