mirror of
https://develop.openfoam.com/Development/openfoam.git
synced 2025-12-28 03:37:59 +00:00
ENH: refine the dataTypes handling
- now distinguish between basic MPI types and user-defined types.
The new front-facing trait UPstream_basic_dataType unwinds components
and other types, but only for MPI fundamental types
(including any aliases)
- additional helper to combine a test for binary operator validity and
basic data type validity, which better expresses intent:
template<class BinaryOp, class T>
UPstream_data_opType;
- relax bit-wise operators to also accept signed integrals
and 'void' generic
This commit is contained in:
@ -51,17 +51,20 @@ Description
|
||||
//- Mapping of some fundamental and aggregate types to MPI data types
|
||||
enum class dataTypes : int
|
||||
{
|
||||
// Builtin Types [8]:
|
||||
DataTypes_begin, //!< Begin builtin types (internal use)
|
||||
type_byte = DataTypes_begin, // also for char, unsigned char
|
||||
// Fundamental Types [10]:
|
||||
Basic_begin,
|
||||
type_byte = Basic_begin,
|
||||
type_int16,
|
||||
type_int32,
|
||||
type_int64,
|
||||
type_uint16,
|
||||
type_uint32,
|
||||
type_uint64,
|
||||
type_float,
|
||||
type_double,
|
||||
type_long_double,
|
||||
invalid
|
||||
invalid,
|
||||
Basic_end = invalid
|
||||
};
|
||||
|
||||
|
||||
@ -69,20 +72,19 @@ enum class dataTypes : int
|
||||
|
||||
// Partial copy from UPstreamTraits.H
|
||||
|
||||
//- A supported UPstream data type (intrinsic or user-defined)
|
||||
//- UPstream data type corresponding to an intrinsic (MPI) type
|
||||
template<class T>
|
||||
struct UPstream_base_dataType : std::false_type
|
||||
struct UPstream_mpi_dataType : std::false_type
|
||||
{
|
||||
static constexpr auto datatype_id = dataTypes::invalid;
|
||||
};
|
||||
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
||||
// Specializations of the above,
|
||||
// each to match the elements of UPstream::dataTypes
|
||||
|
||||
// Specializations to match elements of UPstream::dataTypes
|
||||
#undef defineUPstreamDataTraits
|
||||
#define defineUPstreamDataTraits(TypeId, Type) \
|
||||
template<> struct UPstream_base_dataType<Type> : std::true_type \
|
||||
template<> struct UPstream_mpi_dataType<Type> : std::true_type \
|
||||
{ \
|
||||
static constexpr auto datatype_id = dataTypes::TypeId; \
|
||||
};
|
||||
@ -90,8 +92,10 @@ struct UPstream_base_dataType : std::false_type
|
||||
|
||||
defineUPstreamDataTraits(type_byte, char);
|
||||
defineUPstreamDataTraits(type_byte, unsigned char);
|
||||
defineUPstreamDataTraits(type_int16, int16_t);
|
||||
defineUPstreamDataTraits(type_int32, int32_t);
|
||||
defineUPstreamDataTraits(type_int64, int64_t);
|
||||
defineUPstreamDataTraits(type_uint16, uint16_t);
|
||||
defineUPstreamDataTraits(type_uint32, uint32_t);
|
||||
defineUPstreamDataTraits(type_uint64, uint64_t);
|
||||
defineUPstreamDataTraits(type_float, float);
|
||||
@ -109,8 +113,8 @@ struct UPstream_alias_dataType
|
||||
:
|
||||
std::bool_constant
|
||||
<
|
||||
// Base type (no alias needed)
|
||||
UPstream_base_dataType<std::remove_cv_t<T>>::value ||
|
||||
// Basic MPI type
|
||||
UPstream_mpi_dataType<std::remove_cv_t<T>>::value ||
|
||||
(
|
||||
// Or some int 32/64 type to re-map
|
||||
std::is_integral_v<T>
|
||||
@ -118,15 +122,11 @@ struct UPstream_alias_dataType
|
||||
)
|
||||
>
|
||||
{
|
||||
// Is it using the base type? (no alias needed)
|
||||
static constexpr bool is_base =
|
||||
UPstream_base_dataType<std::remove_cv_t<T>>::value;
|
||||
|
||||
using base = std::conditional_t
|
||||
<
|
||||
UPstream_base_dataType<std::remove_cv_t<T>>::value, // is_base
|
||||
std::remove_cv_t<T>,
|
||||
std::conditional_t
|
||||
UPstream_mpi_dataType<std::remove_cv_t<T>>::value,
|
||||
std::remove_cv_t<T>, // <- using mpi type (no alias)
|
||||
std::conditional_t // <- using alias
|
||||
<
|
||||
(
|
||||
std::is_integral_v<T>
|
||||
@ -138,12 +138,32 @@ struct UPstream_alias_dataType
|
||||
std::conditional_t<std::is_signed_v<T>, int32_t, uint32_t>,
|
||||
std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>
|
||||
>,
|
||||
char // Fallback value (assuming it is contiguous)
|
||||
char // Fallback is a byte (eg, arbitrary contiguous data)
|
||||
>
|
||||
>;
|
||||
|
||||
static constexpr auto datatype_id =
|
||||
UPstream_base_dataType<base>::datatype_id;
|
||||
UPstream_mpi_dataType<base>::datatype_id;
|
||||
};
|
||||
|
||||
|
||||
// Handle int8_t/uint8_t as aliases since 'signed char' etc may be
|
||||
// ambiguous
|
||||
|
||||
//- Map \c int8_t to UPstream::dataTypes::type_byte
|
||||
template<>
|
||||
struct UPstream_alias_dataType<int8_t> : std::true_type
|
||||
{
|
||||
using base = char;
|
||||
static constexpr auto datatype_id = dataTypes::type_byte;
|
||||
};
|
||||
|
||||
//- Map \c uint8_t to UPstream::dataTypes::type_byte
|
||||
template<>
|
||||
struct UPstream_alias_dataType<uint8_t> : std::true_type
|
||||
{
|
||||
using base = unsigned char;
|
||||
static constexpr auto datatype_id = dataTypes::type_byte;
|
||||
};
|
||||
|
||||
|
||||
@ -172,25 +192,30 @@ void print(const char* name, bool showLimits = true)
|
||||
}
|
||||
|
||||
// A declared or deduced MPI type, or aliased
|
||||
std::cout
|
||||
<< " is_mpi=" << UPstream_base_dataType<T>::value
|
||||
<< " (" << int(UPstream_base_dataType<T>::datatype_id) << ")";
|
||||
|
||||
if (UPstream_alias_dataType<T>::value)
|
||||
if constexpr (UPstream_mpi_dataType<T>::value)
|
||||
{
|
||||
if (UPstream_alias_dataType<T>::is_base)
|
||||
{
|
||||
std::cout<< " is_base";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout<< " is_alias ("
|
||||
<< int(UPstream_alias_dataType<T>::datatype_id) << ")";
|
||||
}
|
||||
std::cout
|
||||
<< " is_mpi=("
|
||||
<< int(UPstream_mpi_dataType<T>::datatype_id) << ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout<< " no_alias";
|
||||
std::cout << " is_mpi=(null)";
|
||||
}
|
||||
|
||||
// Any aliases?
|
||||
if constexpr (UPstream_alias_dataType<T>::value)
|
||||
{
|
||||
if constexpr (UPstream_mpi_dataType<T>::value)
|
||||
{
|
||||
std::cout << " alias=base";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout
|
||||
<< " alias=("
|
||||
<< int(UPstream_alias_dataType<T>::datatype_id) << ')';
|
||||
}
|
||||
}
|
||||
|
||||
std::cout<< '\n';
|
||||
@ -217,6 +242,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
std::cout << '\n';
|
||||
print<char>("char");
|
||||
print<signed char>("signed char");
|
||||
print<unsigned char>("unsigned char");
|
||||
print<short>("short");
|
||||
print<int>("int");
|
||||
|
||||
@ -37,35 +37,40 @@ Description
|
||||
#include "vector.H"
|
||||
#include "tensor.H"
|
||||
#include "uLabel.H"
|
||||
#include "MinMax.H"
|
||||
#include "Switch.H"
|
||||
#include "IOstreams.H"
|
||||
#include "UPstream.H"
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace Foam;
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
||||
|
||||
// Just for debugging
|
||||
const List<std::string> dataType_names
|
||||
({
|
||||
"byte",
|
||||
"int32",
|
||||
"int64",
|
||||
"uint32",
|
||||
"uint64",
|
||||
"float",
|
||||
"double",
|
||||
"long_double",
|
||||
namespace Foam
|
||||
{
|
||||
|
||||
// Add in some extras from functional
|
||||
|
||||
//- Map std::plus to \c UPstream::opCodes::op_sum
|
||||
template<>
|
||||
struct UPstream_opType<std::plus<void>> : std::true_type
|
||||
{
|
||||
static constexpr auto opcode_id = UPstream::opCodes::op_sum;
|
||||
};
|
||||
|
||||
|
||||
//- Map 'signed char' to UPstream::dataTypes::type_byte
|
||||
// Caution with: may be identical to int8_t mapping!!
|
||||
#if 0
|
||||
template<>
|
||||
struct UPstream_alias_dataType<signed char> : std::true_type
|
||||
{
|
||||
using base = char;
|
||||
static constexpr auto datatype_id = UPstream::dataTypes::type_byte;
|
||||
};
|
||||
#endif
|
||||
|
||||
"float(2)",
|
||||
"double(2)",
|
||||
"float(3)",
|
||||
"double(3)",
|
||||
"float(6)",
|
||||
"double(6)",
|
||||
"float(9)",
|
||||
"double(9)"
|
||||
});
|
||||
|
||||
//- Test for pTraits typeName member : default is false
|
||||
template<class T, class = void>
|
||||
@ -82,24 +87,93 @@ struct check_has_typeName
|
||||
std::true_type
|
||||
{};
|
||||
|
||||
|
||||
// Possible future change...
|
||||
// //- A supported UPstream data type (intrinsic or user-defined)
|
||||
// template<>
|
||||
// struct UPstream_base_dataType<complex> : std::true_type
|
||||
// {
|
||||
// static constexpr auto datatype_id = []()
|
||||
// {
|
||||
// if constexpr (sizeof(complex) == 2*sizeof(float))
|
||||
// return UPstream::dataTypes::type_2float;
|
||||
// else
|
||||
// return UPstream::dataTypes::type_2double;
|
||||
// }();
|
||||
// };
|
||||
} // End namespace Foam
|
||||
|
||||
|
||||
template<class T>
|
||||
void printTypeName(const bool showSize = false)
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
||||
|
||||
// Just for debugging
|
||||
static const Foam::List<std::string> dataType_names
|
||||
({
|
||||
"byte",
|
||||
"int16",
|
||||
"int32",
|
||||
"int64",
|
||||
"uint16",
|
||||
"uint32",
|
||||
"uint64",
|
||||
"float",
|
||||
"double",
|
||||
"long_double",
|
||||
|
||||
"float[3]",
|
||||
"double[3]",
|
||||
"float[6]",
|
||||
"double[6]",
|
||||
"float[9]",
|
||||
"double[9]"
|
||||
});
|
||||
|
||||
// Just for debugging
|
||||
static const Foam::List<std::string> opType_names
|
||||
({
|
||||
"op_min",
|
||||
"op_max",
|
||||
"op_sum",
|
||||
"op_prod",
|
||||
"op_bool_and",
|
||||
"op_bool_or",
|
||||
"op_bool_xor",
|
||||
"op_bit_and",
|
||||
"op_bit_or",
|
||||
"op_bit_xor",
|
||||
"op_replace",
|
||||
"op_no_op"
|
||||
});
|
||||
|
||||
|
||||
using namespace Foam;
|
||||
|
||||
void printDataTypeId(UPstream::dataTypes datatype_id)
|
||||
{
|
||||
if (datatype_id != UPstream::dataTypes::invalid)
|
||||
{
|
||||
const int index = int(datatype_id);
|
||||
if (index < dataType_names.size())
|
||||
{
|
||||
Info<< dataType_names[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< '(' << index << ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void printOpCodeId(UPstream::opCodes opcode_id)
|
||||
{
|
||||
if (opcode_id != UPstream::opCodes::invalid)
|
||||
{
|
||||
const int index = int(opcode_id);
|
||||
if (index < opType_names.size())
|
||||
{
|
||||
Info<< ':' << opType_names[index].c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< '(' << index << ')';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< "(null)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class T, bool showSize = false>
|
||||
void printTypeName()
|
||||
{
|
||||
// Both float and double have pTraits typeName = "scalar"!
|
||||
if constexpr (std::is_same_v<float, std::remove_cv_t<T>>)
|
||||
@ -118,12 +192,13 @@ void printTypeName(const bool showSize = false)
|
||||
{
|
||||
Info<< typeid(T).name();
|
||||
}
|
||||
if (showSize)
|
||||
if constexpr (showSize)
|
||||
{
|
||||
Info<< " (" << sizeof(T) << " bytes)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class Type, bool UseTypeName = true>
|
||||
void printPstreamTraits(const std::string_view name = std::string_view())
|
||||
{
|
||||
@ -133,55 +208,111 @@ void printPstreamTraits(const std::string_view name = std::string_view())
|
||||
{
|
||||
Info<< name << ' ';
|
||||
}
|
||||
|
||||
if constexpr (UseTypeName)
|
||||
{
|
||||
printTypeName<Type>(true);
|
||||
printTypeName<Type, true>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< typeid(Type).name();
|
||||
Info<< " (" << sizeof(Type) << " bytes)";
|
||||
Info<< typeid(Type).name() << " (" << sizeof(Type) << " bytes)";
|
||||
}
|
||||
|
||||
{
|
||||
using cmpt = typename Foam::pTraits_cmptType<Type>::type;
|
||||
|
||||
if constexpr (!std::is_same_v<Type, cmpt>)
|
||||
{
|
||||
Info<< ", cmpt:";
|
||||
|
||||
if constexpr (UseTypeName)
|
||||
{
|
||||
printTypeName<cmpt, true>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< typeid(cmpt).name() << " (" << sizeof(cmpt) << " bytes)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Info<< ", cmpt:";
|
||||
printTypeName<typename Foam::pTraits_cmptType<Type>::type>(true);
|
||||
|
||||
Info<< nl
|
||||
<< " is_contiguous:"
|
||||
<< is_contiguous<Type>::value
|
||||
<< ", is base:"
|
||||
<< UPstream_base_dataType<Type>::value
|
||||
<< ", is cmpt:"
|
||||
<< UPstream_dataType<Type>::value << nl;
|
||||
|
||||
Info<< "is base:"
|
||||
<< UPstream_base_dataType<Type>::value
|
||||
<< " (type:" << int(UPstream_base_dataType<Type>::datatype_id)
|
||||
<< ") is alias:" << UPstream_alias_dataType<Type>::value
|
||||
<< " (type:" << int(UPstream_alias_dataType<Type>::datatype_id)
|
||||
<< ")" << nl;
|
||||
|
||||
<< is_contiguous<Type>::value;
|
||||
|
||||
if constexpr (UPstream_mpi_dataType<Type>::value)
|
||||
{
|
||||
int index = int(UPstream_base_dataType<Type>::datatype_id);
|
||||
Info<< "datatype: " << index;
|
||||
Info<< ", is_mpi=("
|
||||
<< int(UPstream_mpi_dataType<Type>::datatype_id) << ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << ", is_mpi=(null)";
|
||||
}
|
||||
if constexpr (UPstream_user_dataType<Type>::value)
|
||||
{
|
||||
Info<< ", is_user=("
|
||||
<< int(UPstream_user_dataType<Type>::datatype_id) << ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << ", is_user=(null)";
|
||||
}
|
||||
if constexpr (UPstream_any_dataType<Type>::value)
|
||||
{
|
||||
Info<< ", is_any=("
|
||||
<< int(UPstream_any_dataType<Type>::datatype_id) << ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << ", is_any=(null)";
|
||||
}
|
||||
|
||||
if (index < dataType_names.size())
|
||||
{
|
||||
Info<< ' ' << dataType_names[index];
|
||||
}
|
||||
Info<< nl;
|
||||
// Any aliases?
|
||||
if constexpr
|
||||
(
|
||||
UPstream_alias_dataType<Type>::value
|
||||
&& !UPstream_mpi_dataType<Type>::value
|
||||
)
|
||||
{
|
||||
Info<< ", alias=("
|
||||
<< int(UPstream_alias_dataType<Type>::datatype_id) << ')';
|
||||
}
|
||||
|
||||
Info<< " base-type:" << int(UPstream_basic_dataType<Type>::datatype_id)
|
||||
<< " data-type:" << int(UPstream_dataType<Type>::datatype_id)
|
||||
<< nl;
|
||||
|
||||
if constexpr (UPstream_basic_dataType<Type>::value)
|
||||
{
|
||||
Info<< " base-type=";
|
||||
printDataTypeId(UPstream_basic_dataType<Type>::datatype_id);
|
||||
}
|
||||
else if constexpr (UPstream_dataType<Type>::value)
|
||||
{
|
||||
Info<< " data-type=";
|
||||
printDataTypeId(UPstream_dataType<Type>::datatype_id);
|
||||
}
|
||||
|
||||
{
|
||||
// Use element or component type (or byte-wise) for data type
|
||||
using base = typename UPstream_dataType<Type>::base;
|
||||
constexpr auto datatype = UPstream_dataType<Type>::datatype_id;
|
||||
|
||||
Info<< "datatype => ";
|
||||
printTypeName<base>();
|
||||
Info<< " (" << sizeof(Type)/sizeof(base) << " elems)" << nl
|
||||
<< "datatype: " << static_cast<int>(datatype) << nl;
|
||||
Info<< " : ";
|
||||
if constexpr (UseTypeName)
|
||||
{
|
||||
printTypeName<base, true>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< typeid(base).name() << " (" << sizeof(base) << " bytes)";
|
||||
}
|
||||
|
||||
Info<< " cmpt-type=";
|
||||
printDataTypeId(UPstream_dataType<Type>::datatype_id);
|
||||
Info<< " count=" << UPstream_dataType<Type>::size(1);
|
||||
Info<< nl;
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,15 +321,44 @@ template<class BinaryOp>
|
||||
void printOpCodeTraits(BinaryOp bop, std::string_view name)
|
||||
{
|
||||
Info<< "op: " << name << ' ';
|
||||
if constexpr (UPstream_opType<BinaryOp>::value)
|
||||
|
||||
printOpCodeId(UPstream_opType<BinaryOp>::opcode_id);
|
||||
Info<< nl;
|
||||
}
|
||||
|
||||
|
||||
template<class DataType, class BinaryOp>
|
||||
void printOpCodeTraits(BinaryOp bop, std::string_view name)
|
||||
{
|
||||
Info<< "op: " << name << ' ';
|
||||
|
||||
printOpCodeId(UPstream_opType<BinaryOp>::opcode_id);
|
||||
|
||||
if constexpr (!std::is_void_v<DataType>)
|
||||
{
|
||||
Info<< "supported";
|
||||
if constexpr (UPstream_basic_dataType<DataType>::value)
|
||||
{
|
||||
Info<< " [supported type]";
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< " [disabled]";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Info<< "unknown";
|
||||
}
|
||||
Info<< ": " << int(UPstream_opType<BinaryOp>::opcode_id) << nl;
|
||||
Info<< nl;
|
||||
}
|
||||
|
||||
|
||||
template<class DataType, class BinaryOp>
|
||||
void print_data_opType(BinaryOp bop, std::string_view name)
|
||||
{
|
||||
Info<< "op: " << name << ' ';
|
||||
|
||||
printOpCodeId(UPstream_data_opType<BinaryOp, DataType>::opcode_id);
|
||||
|
||||
const bool ok = UPstream_data_opType<BinaryOp, DataType>::value;
|
||||
|
||||
Info<< " okay=" << ok << nl;
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +370,16 @@ int main()
|
||||
printPstreamTraits<bool>();
|
||||
printPstreamTraits<label>();
|
||||
|
||||
printPstreamTraits<char, false>("<char>");
|
||||
printPstreamTraits<signed char, false>("<signed char>");
|
||||
printPstreamTraits<unsigned char, false>("<unsigned char>");
|
||||
|
||||
printPstreamTraits<int8_t, false>("<int8_t>");
|
||||
printPstreamTraits<uint8_t, false>("<uint8_t>");
|
||||
|
||||
printPstreamTraits<int16_t, false>("<int16_t>");
|
||||
printPstreamTraits<uint16_t, false>("<uint16_t>");
|
||||
|
||||
printPstreamTraits<int>("<int>");
|
||||
printPstreamTraits<long>("<long>");
|
||||
printPstreamTraits<unsigned>("<unsigned>");
|
||||
@ -258,6 +428,35 @@ int main()
|
||||
printOpCodeTraits(bitAndOp<unsigned>{}, "bitAnd<unsigned>");
|
||||
printOpCodeTraits(bitOrOp<unsigned>{}, "bitOr<unsigned>");
|
||||
|
||||
printOpCodeTraits<vector>(sumOp<vector>{}, "sum");
|
||||
printOpCodeTraits(sumOp<scalarMinMax>{}, "sum");
|
||||
|
||||
printOpCodeTraits(std::plus<>{}, "sum");
|
||||
printOpCodeTraits<bool>(std::plus<>{}, "sum");
|
||||
printOpCodeTraits<vector>(std::plus<>{}, "sum");
|
||||
|
||||
|
||||
// Expect success
|
||||
Info<< nl << "expect success" << nl;
|
||||
print_data_opType<vector>(maxOp<scalar>(), "maxOp(scalar)");
|
||||
print_data_opType<unsigned>(bitOrOp<unsigned>(), "bitOrOp(unsigned)");
|
||||
print_data_opType<uint8_t>(bitOrOp<uint8_t>(), "bitOrOp(uint8_t)");
|
||||
print_data_opType<uint16_t>(bitOrOp<uint16_t>(), "bitOrOp(uint16_t)");
|
||||
|
||||
// Even allow signed integrals
|
||||
print_data_opType<int>(bitOrOp<int>(), "bitOrOp(int)");
|
||||
print_data_opType<int8_t>(bitOrOp<int8_t>(), "bitOrOp(int8_t)");
|
||||
|
||||
// Failure - supported op, unsupported data type.
|
||||
Info<< nl << "expect failure" << nl;
|
||||
print_data_opType<bool>(maxOp<scalar>(), "maxOp(scalar, bool)");
|
||||
print_data_opType<bool>(bitOrOp<unsigned>(), "bitOrOp(unsigned, bool)");
|
||||
|
||||
// False positives. Failure - supported op, unsupported data type.
|
||||
Info<< nl << "false positives" << nl;
|
||||
print_data_opType<void>(maxOp<bool>(), "maxOp(bool, void)");
|
||||
print_data_opType<float>(bitOrOp<unsigned>(), "bitOrOp(unsigned, float)");
|
||||
|
||||
Info<< nl << "End\n" << endl;
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user